LIGHT

  • News
  • Docs
  • Community
  • Reddit
  • GitHub

Command

This module contains command side repository and service implementation and it is a foundation of command side restful service.

First let’s take a look at Todo class which is a data object.

Todo.java

package com.networknt.tram.todolist.command;


import com.networknt.eventuate.jdbc.IdGenerator;
import com.networknt.eventuate.jdbc.IdGeneratorImpl;

public class Todo {

  private static IdGenerator idGenerator = new IdGeneratorImpl();
  private String id;

  private String title;

  private boolean completed;

  private int executionOrder;

  public Todo() {
  }

  public Todo(String title, boolean completed, int executionOrder) {
    this.id = idGenerator.genId().asString();
    this.title = title;
    this.completed = completed;
    this.executionOrder = executionOrder;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getId() {
    return id;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public boolean isCompleted() {
    return completed;
  }

  public void setCompleted(boolean completed) {
    this.completed = completed;
  }

  public int getExecutionOrder() {
    return executionOrder;
  }

  public void setExecutionOrder(int executionOrder) {
    this.executionOrder = executionOrder;
  }
}

We need a repository interface to save Todo object into database.

TodoRepository.java

package com.networknt.tram.todolist.command;


import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public interface TodoRepository {

    void setConnection(Connection connection);

    List<Todo>  getAll();

    Todo findOne(String id);

    Todo save(Todo todo) throws SQLException;

    Todo update(Todo todo) throws TodoNotFoundException, SQLException;

    void delete(String id) throws SQLException;
}

And the implementation for the interface is here.

TodoRepositoryImpl.java

package com.networknt.tram.todolist.command;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class TodoRepositoryImpl implements TodoRepository {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    private Connection connection;

    public TodoRepositoryImpl() {

    }

    public TodoRepositoryImpl(Connection connection) {
        this.connection = connection;
    }

    public void setConnection(Connection connection) {
        this.connection = connection;
    }


    @Override
    public List<Todo> getAll() {
        List<Todo> todos = new ArrayList<>();

        try {
            String psSelect = "SELECT ID, TITLE, COMPLETED, ORDER_ID FROM todo_db.TODO WHERE ACTIVE_FLG = 'Y' order by ORDER_ID asc";
            PreparedStatement stmt = connection.prepareStatement(psSelect);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                Todo todo = new Todo();
                todo.setTitle(rs.getString("ID"));
                todo.setTitle(rs.getString("TITLE"));
                todo.setCompleted(rs.getBoolean("COMPLETED"));
                todo.setExecutionOrder(rs.getInt("ORDER_ID"));
                todos.add(todo);
            }
        } catch (SQLException e) {
            logger.error("SqlException:", e);
        }

        return todos;
    }

    @Override
    public Todo findOne(String id) {
        Todo todo = null;
        try {
            String psSelect = "SELECT ID, TITLE, COMPLETED, ORDER_ID FROM todo_db.TODO WHERE ACTIVE_FLG = 'Y' AND ID = ?";
            PreparedStatement stmt = connection.prepareStatement(psSelect);
            stmt.setString(1, id);
            ResultSet rs = stmt.executeQuery();
            if (rs == null || rs.getFetchSize() > 1) {
                logger.error("incorrect fetch result {}", id);
            }
            while (rs.next()) {
                todo = new Todo();
                todo.setId(rs.getString("ID"));
                todo.setTitle(rs.getString("TITLE"));
                todo.setCompleted(rs.getBoolean("COMPLETED"));
                todo.setExecutionOrder(rs.getInt("ORDER_ID"));
            }
        } catch (SQLException e) {
            logger.error("SqlException:", e);
        }

        return todo;
    }

    @Override
    public Todo save(Todo todo) throws SQLException{
            String psInsert = "INSERT INTO todo_db.TODO (ID, TITLE, COMPLETED, ORDER_ID) VALUES (?, ?, ?, ?)";
            try (PreparedStatement stmt = connection.prepareStatement(psInsert)) {
                stmt.setString(1, todo.getId());
                stmt.setString(2, todo.getTitle());
                stmt.setBoolean(3, todo.isCompleted());
                stmt.setInt(4, todo.getExecutionOrder());
                int count = stmt.executeUpdate();
                if (count != 1) {
                    logger.error("Failed to insert TODO: {}", todo.getId());
                } else {
                    return todo;
                }
            } catch (SQLException e) {
                logger.error("SqlException:", e);
                throw new SQLException(e);
            }


            return null;
    }

    @Override
    public Todo update(Todo todo)  throws SQLException{
            String psUpdate = "UPDATE todo_db.TODO SET TITLE=?, COMPLETED=?, ORDER_ID=? WHERE ID=? ";
            try (PreparedStatement stmt = connection.prepareStatement(psUpdate)) {
                stmt.setString(1, todo.getTitle());
                stmt.setBoolean(2, todo.isCompleted());
                stmt.setInt(3, todo.getExecutionOrder());
                stmt.setString(4, todo.getId());
                int count = stmt.executeUpdate();
                if (count != 1) {
                    logger.error("Failed to update TODO: {}", todo.getId());
                } else {
                    return todo;
                }

            } catch (SQLException e) {
                logger.error("SqlException:", e);
                throw new SQLException(e);
            }


            return null;
    }

    @Override
    public void delete(String id)  throws SQLException{
            String psDelete = "UPDATE todo_db.TODO SET ACTIVE_FLG = 'N' WHERE ID = ?";
            try (PreparedStatement psEntity = connection.prepareStatement(psDelete)) {
                psEntity.setString(1, id);
                int count = psEntity.executeUpdate();
                if (count != 1) {
                    logger.error("Failed to delete TODO: {}", id);
                }
            } catch (SQLException e) {
                logger.error("SqlException:", e);
                throw new SQLException(e);
            }



    }


}

As you have noticed, the database connection is passed in from the constructor so that all db operation will join a JDBC transaction with the same connection.

The following is a service class that wires in both database actions together. Update todo object and insert an event to message table in the same transaction.

TodoCommandService.java

package com.networknt.tram.todolist.command;

import com.networknt.eventuate.jdbc.IdGenerator;
import com.networknt.eventuate.jdbc.IdGeneratorImpl;
import com.networknt.tram.event.common.DomainEvent;
import com.networknt.tram.event.publisher.DomainEventPublisher;
import com.networknt.tram.event.publisher.DomainEventPublisherImpl;
import com.networknt.tram.message.producer.MessageProducer;
import com.networknt.tram.message.producer.jdbc.MessageProducerJdbcConnectionImpl;
import com.networknt.tram.todolist.common.TodoCreated;
import com.networknt.tram.todolist.common.TodoDeleted;
import com.networknt.tram.todolist.common.TodoUpdated;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

import static java.util.Arrays.asList;


public class TodoCommandService {

  protected Logger logger = LoggerFactory.getLogger(getClass());

  private TodoRepository todoRepository;

  private DomainEventPublisher domainEventPublisher;

  private IdGenerator idGenerator;

  private DataSource dataSource;

  public TodoCommandService( DataSource dataSource,IdGenerator idGenerator) {
    this.dataSource = dataSource;
    this.idGenerator = idGenerator;
  }

  public Todo create(CreateTodoRequest createTodoRequest) {

      Todo todo = new Todo(createTodoRequest.getTitle(), createTodoRequest.isCompleted(), createTodoRequest.getOrder());
      try (final Connection connection = dataSource.getConnection()) {
          connection.setAutoCommit(false);
          this.todoRepository = new TodoRepositoryImpl(connection);
          MessageProducer messageProducer = new MessageProducerJdbcConnectionImpl(connection,idGenerator);
          this.domainEventPublisher = new DomainEventPublisherImpl(messageProducer);

          todo = todoRepository.save(todo);
          publishTodoEvent(todo, new TodoCreated(todo.getTitle(), todo.isCompleted(), todo.getExecutionOrder()));

          connection.commit();
      } catch (SQLException e) {
          logger.error("SqlException:", e);
      }

      return todo;
  }

  private void publishTodoEvent(Todo todo, DomainEvent... domainEvents) {
    domainEventPublisher.publish(Todo.class, todo.getId(), asList(domainEvents));
  }

  private void publishTodoEvent(String id, DomainEvent... domainEvents) {
    domainEventPublisher.publish(Todo.class, id, asList(domainEvents));
  }

  public Todo update(String id, UpdateTodoRequest updateTodoRequest) throws TodoNotFoundException {
      try (final Connection connection = dataSource.getConnection()) {
          connection.setAutoCommit(false);
          this.todoRepository = new TodoRepositoryImpl(connection);
          Todo todo = todoRepository.findOne(id);
          if (todo == null) {
              throw new TodoNotFoundException(id);
          }
          todo.setTitle(updateTodoRequest.getTitle());
          todo.setCompleted(updateTodoRequest.isCompleted());
          todo.setExecutionOrder(updateTodoRequest.getOrder());
          todoRepository.update(todo);

          MessageProducer messageProducer = new MessageProducerJdbcConnectionImpl(connection, new IdGeneratorImpl());
          this.domainEventPublisher = new DomainEventPublisherImpl(messageProducer);
          publishTodoEvent(todo, new TodoUpdated(todo.getTitle(), todo.isCompleted(), todo.getExecutionOrder()));

          connection.commit();

          return todo;
      } catch (SQLException e) {
          logger.error("SqlException:", e);
      }

    return null;
  }

  public void delete(String id) {
      try (final Connection connection = dataSource.getConnection()) {
          this.todoRepository = new TodoRepositoryImpl(connection);
          MessageProducer messageProducer = new MessageProducerJdbcConnectionImpl(connection,idGenerator);
          this.domainEventPublisher = new DomainEventPublisherImpl(messageProducer);

          todoRepository.delete(id);
          publishTodoEvent(id, new TodoDeleted());

      } catch (SQLException e) {
          logger.error("SqlException:", e);
      }

  }

  public Todo findOne(String id) {
      Todo todo = null;
      try (final Connection connection = dataSource.getConnection()) {
          this.todoRepository = new TodoRepositoryImpl(connection);
          todo = todoRepository.findOne(id);

      } catch (SQLException e) {
          logger.error("SqlException:", e);
      }
      return todo;
  }
}

Pay attention on how connection is handled. First, the autocommit is turned off and the same connection object is passed into TodoResposity and MessageProducer implementations.

There is a unit test to ensure that above service is working.

package com.networknt.tram.todolist.command;



import com.networknt.service.SingletonServiceFactory;
import com.networknt.tram.todolist.common.Utils;
import org.h2.tools.RunScript;
import org.junit.Assert;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.SQLException;


public class CommandModuleTest {
  public static DataSource ds;

  static {
    ds = SingletonServiceFactory.getBean(DataSource.class);
    try (Connection connection = ds.getConnection()) {
      // Runscript doesn't work need to execute batch here.
      String schemaResourceName = "/todolist-example-h2-ddl.sql";
      InputStream in = CommandModuleTest.class.getResourceAsStream(schemaResourceName);

      if (in == null) {
        throw new RuntimeException("Failed to load resource: " + schemaResourceName);
      }
      InputStreamReader reader = new InputStreamReader(in);
      RunScript.execute(connection, reader);

    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

  private TodoCommandService todoCommandService = SingletonServiceFactory.getBean(TodoCommandService.class);

  @Test
  public void testCreate() {
    String title = Utils.generateUniqueString();
    String id = todoCommandService.create(new CreateTodoRequest(title, false, 0)).getId();
    Todo todo = todoCommandService.findOne(id);
    Assert.assertNotNull(todo);
    Assert.assertEquals(title, todo.getTitle());
  }

  @Test
  public void testUpdate() throws TodoNotFoundException {
    Todo todo = todoCommandService.create(new CreateTodoRequest(Utils.generateUniqueString(), false, 9));
    String title = Utils.generateUniqueString();
    todoCommandService.update(todo.getId(), new UpdateTodoRequest(title, false, 0));
    todo = todoCommandService.findOne(todo.getId());
    Assert.assertNotNull(todo);
    Assert.assertEquals(title, todo.getTitle());
  }

  @Test
  public void testDelete() {
    Todo todo = todoCommandService.create(new CreateTodoRequest(Utils.generateUniqueString(), false, 9));
    todoCommandService.delete(todo.getId());
    Assert.assertNull(todoCommandService.findOne(todo.getId()));
  }
}

The unit test is using H2 database and the script todolist-example-h2-ddl.sql can be found in src/test/resources folder. There is also a service.yml config file to define the DataSource and TodoCommandServer as singletons.

Here is the content of service.yml

singletons:
- javax.sql.DataSource:
  - com.zaxxer.hikari.HikariDataSource:
      DriverClassName: org.h2.jdbcx.JdbcDataSource
      jdbcUrl: jdbc:h2:~/test
      username: sa
      password: sa
- com.networknt.eventuate.jdbc.IdGenerator:
  - com.networknt.eventuate.jdbc.IdGeneratorImpl
- com.networknt.tram.todolist.command.TodoCommandService:
  - com.networknt.tram.todolist.command.TodoCommandService

In the next step, we are going to take a look at view side common commponents.

  • About Light
    • Overview
    • Testimonials
    • What is Light
    • Features
    • Principles
    • Benefits
    • Roadmap
    • Community
    • Articles
    • Videos
    • License
    • Why Light Platform
  • Getting Started
    • Get Started Overview
    • Environment
    • Light Codegen Tool
    • Light Rest 4j
    • Light Tram 4j
    • Light Graphql 4j
    • Light Hybrid 4j
    • Light Eventuate 4j
    • Light Oauth2
    • Light Portal Service
    • Light Proxy Server
    • Light Router Server
    • Light Config Server
    • Light Saga 4j
    • Light Session 4j
    • Webserver
    • Websocket
    • Spring Boot Servlet
  • Architecture
    • Architecture Overview
    • API Category
    • API Gateway
    • Architecture Patterns
    • CQRS
    • Eco System
    • Event Sourcing
    • Fail Fast vs Fail Slow
    • Integration Patterns
    • JavaEE declining
    • Key Distribution
    • Microservices Architecture
    • Microservices Monitoring
    • Microservices Security
    • Microservices Traceability
    • Modular Monolith
    • Platform Ecosystem
    • Plugin Architecture
    • Scalability and Performance
    • Serverless
    • Service Collaboration
    • Service Mesh
    • SOA
    • Spring is bloated
    • Stages of API Adoption
    • Transaction Management
    • Microservices Cross-cutting Concerns Options
    • Service Mesh Plus
    • Service Discovery
  • Design
    • Design Overview
    • Design First vs Code First
    • Desgin Pattern
    • Service Evolution
    • Consumer Contract and Consumer Driven Contract
    • Handling Partial Failure
    • Idempotency
    • Server Life Cycle
    • Environment Segregation
    • Database
    • Decomposition Patterns
    • Http2
    • Test Driven
    • Multi-Tenancy
    • Why check token expiration
    • WebServices to Microservices
  • Cross-Cutting Concerns
    • Concerns Overview
  • API Styles
    • Light-4j for absolute performance
    • Style Overview
    • Distributed session on IMDG
    • Hybrid Serverless Modularized Monolithic
    • Kafka - Event Sourcing and CQRS
    • REST - Representational state transfer
    • Web Server with Light
    • Websocket with Light
    • Spring Boot Integration
    • Single Page Application
    • GraphQL - A query language for your API
    • Light IBM MQ
    • Light AWS Lambda
    • Chaos Monkey
  • Infrastructure Services
    • Service Overview
    • Light Proxy
    • Light Mesh
    • Light Router
    • Light Portal
    • Messaging Infrastructure
    • Centralized Logging
    • COVID-19
    • Light OAuth2
    • Metrics and Alerts
    • Config Server
    • Tokenization
    • Light Controller
  • Tool Chain
    • Tool Chain Overview
  • Utility Library
  • Service Consumer
    • Service Consumer
  • Development
    • Development Overview
  • Deployment
    • Deployment Overview
    • Frontend Backend
    • Linux Service
    • Windows Service
    • Install Eventuate on Windows
    • Secure API
    • Client vs light-router
    • Memory Limit
    • Deploy to Kubernetes
  • Benchmark
    • Benchmark Overview
  • Tutorial
    • Tutorial Overview
  • Troubleshooting
    • Troubleshoot
  • FAQ
    • FAQ Overview
  • Milestones
  • Contribute
    • Contribute to Light
    • Development
    • Documentation
    • Example
    • Tutorial
“Command” was last updated: July 5, 2021: fixes #275 checked and corrected grammar/spelling for majority of pages (#276) (b3bbb7b)
Improve this page
  • News
  • Docs
  • Community
  • Reddit
  • GitHub
  • About Light
  • Getting Started
  • Architecture
  • Design
  • Cross-Cutting Concerns
  • API Styles
  • Infrastructure Services
  • Tool Chain
  • Utility Library
  • Service Consumer
  • Development
  • Deployment
  • Benchmark
  • Tutorial
  • Troubleshooting
  • FAQ
  • Milestones
  • Contribute