Skip to the content.

CommandMosaic

A Java framework for building services using the Command pattern

License

Table of Contents

Why CommandMosaic?

While REST APIs work well for resource-oriented operations, modeling every business operation as CRUD (Create, Read, Update, Delete) can become challenging with complex domain logic:

CommandMosaic takes a different approach: one dispatch endpoint, many commands.

Key Features

Quick Example

Define a command

package com.example.commands;

import org.commandmosaic.api.Command;
import org.commandmosaic.api.CommandContext;
import org.commandmosaic.api.Parameter;
import org.springframework.beans.factory.annotation.Autowired;

public class ProcessPayment implements Command<PaymentResult> {

    @Autowired
    private PaymentService paymentService;

    @Parameter
    private String orderId;
    
    @Parameter
    private BigDecimal amount;

    @Override
    public PaymentResult execute(CommandContext context) {
        return paymentService.processPayment(orderId, amount);
    }
}

Invoke it

From Java code:

commandDispatcher.dispatchCommand(new ProcessPayment(orderId, amount), context);

From a REST client:

curl -X POST https://api.example.com/dispatch \
  -H "Content-Type: application/json" \
  -d '{
    "command": "ProcessPayment",
    "parameters": {
      "orderId": "ORD-123",
      "amount": 99.99
    },
    "protocol": "CM/1.0"
  }'

That’s it. No routes to define, no controllers to write, no API Gateway configuration to update.

Architecture

CommandMosaic separates what to execute from how it’s invoked:

Client Request → CommandDispatcher → Command.execute() → Response

Commands are:

When to Use CommandMosaic

Good fit:

Maybe not:

Core Concepts

Before diving into integration, let’s understand the key concepts:

Commands

Commands are self-contained units of business logic that implement the Command<T> interface:

public class ProcessPayment implements Command<PaymentResult> {
    @Parameter
    private String orderId;
    
    @Parameter
    private BigDecimal amount;

    @Override
    public PaymentResult execute(CommandContext context) {
        // Your business logic here
        return new PaymentResult(...);
    }
}

Command Dispatcher

The CommandDispatcher is responsible for:

Request Format

All integrations use the same JSON request format:

{
  "command": "ProcessPayment",
  "parameters": {
    "orderId": "ORD-123",
    "amount": 99.99
  },
  "auth": {
    "token": "your-auth-token"
  },
  "protocol": "CM/1.0"
}

Getting Started

Choosing Your Integration

CommandMosaic supports multiple runtime environments. Choose the one that matches your application:

Integration Type Use Case Artifact ID
Plain Java Standalone applications, internal command patterns commandmosaic-plain-java
Spring Boot Modern web applications, REST APIs commandmosaic-spring-boot-autoconfigure
Servlet Legacy Java EE/Jakarta EE apps, JSP apps, EJB servers commandmosaic-servlet
AWS Lambda (Plain) Serverless functions without Spring commandmosaic-aws-lambda-plain-java
AWS Lambda (Spring Boot) Serverless functions with Spring Boot commandmosaic-aws-lambda-springboot

Integration Guides

Plain Java Applications

Use this when: You want to use the Command pattern within a standalone Java application without exposing commands as a web service.

1. Add Dependency

<dependency>
    <groupId>org.commandmosaic</groupId>
    <artifactId>commandmosaic-plain-java</artifactId>
    <version>2.0.0</version>
</dependency>

2. Configure and Create Dispatcher

import org.commandmosaic.api.CommandDispatcher;
import org.commandmosaic.api.configuration.CommandDispatcherConfiguration;
import org.commandmosaic.api.factory.CommandDispatcherFactory;
import org.commandmosaic.plain.PlainCommandDispatcherFactory;

// Configure the dispatcher
CommandDispatcherConfiguration config = CommandDispatcherConfiguration.builder()
    .rootPackage("com.example.commands")  // Only commands in this package are allowed
    .build();

// Create the dispatcher
CommandDispatcherFactory factory = PlainCommandDispatcherFactory.getInstance();
CommandDispatcher dispatcher = factory.getCommandDispatcher(config);

// Execute commands
GreetingCommand command = new GreetingCommand("Alice");
String result = dispatcher.dispatchCommand(command, null);

3. Implement Your Commands

package com.example.commands;

import org.commandmosaic.api.Command;
import org.commandmosaic.api.CommandContext;
import org.commandmosaic.api.Parameter;

public class GreetingCommand implements Command<String> {
    
    @Parameter
    private String name;
    
    public GreetingCommand() {
        // Required no-arg constructor
    }
    
    public GreetingCommand(String name) {
        this.name = name;
    }
    
    @Override
    public String execute(CommandContext context) {
        return "Hello, " + name + "!";
    }
}

Best for: Internal application logic, organizing code with Command pattern, testing command logic.

📚 See complete sample application →


Spring Boot Web Applications

Use this when: You’re building a modern web application with Spring Boot and want to expose commands via REST API.

1. Add Dependency

<dependency>
    <groupId>org.commandmosaic</groupId>
    <artifactId>commandmosaic-spring-boot-autoconfigure</artifactId>
    <version>2.0.0</version>
</dependency>

2. Configure CommandMosaic

Create a configuration class with the dispatcher configuration:

package com.example.config;

import org.commandmosaic.api.configuration.CommandDispatcherConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CommandMosaicConfig {
    
    @Bean
    public CommandDispatcherConfiguration commandDispatcherConfig() {
        return CommandDispatcherConfiguration.builder()
            .rootPackage("com.example.commands")
            .build();
    }
}

3. Expose Commands via REST Endpoint

Create a controller that delegates to the dispatcher:

package com.example.api;

import org.commandmosaic.api.server.CommandDispatcherServer;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

@RestController
@RequestMapping("/api")
public class CommandResource {

    private final CommandDispatcherServer dispatcherServer;

    public CommandResource(CommandDispatcherServer dispatcherServer) {
        this.dispatcherServer = dispatcherServer;
    }

    @PostMapping("/dispatch")
    public void dispatch(InputStream is, OutputStream os) throws IOException {
        dispatcherServer.serviceRequest(is, os);
    }
}

4. Implement Commands as Spring Beans

Commands are Spring-managed beans with full dependency injection support:

package com.example.commands;

import org.commandmosaic.api.Command;
import org.commandmosaic.api.CommandContext;
import org.commandmosaic.api.Parameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

public class CreateOrder implements Command<OrderResult> {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private PaymentService paymentService;
    
    @Parameter
    private String customerId;
    
    @Parameter
    private List<OrderItem> items;
    
    @Override
    @Transactional
    public OrderResult execute(CommandContext context) {
        // Full Spring features: dependency injection, transactions, etc.
        // Commands are created as prototype-scoped beans automatically
        Order order = orderService.createOrder(customerId, items);
        paymentService.initializePayment(order);
        return new OrderResult(order.getId());
    }
}

Important: Do NOT annotate commands with @Component or @Service. Commands have @Parameter fields that receive per-request values, so they must be created fresh for each execution. CommandMosaic automatically registers commands as prototype-scoped beans in Spring’s bean factory, ensuring thread-safety and proper parameter injection.

Note on Performance: Prototype-scoped beans have a small creation overhead (~0.1-0.5ms per command), but this is negligible compared to typical I/O operations (database queries, network calls). For most web applications, this represents <2% of total request time. See Performance Implications for details.

5. Call Your API

curl -X POST http://localhost:8080/api/dispatch \
  -H "Content-Type: application/json" \
  -d '{
    "command": "CreateOrder",
    "parameters": {
      "customerId": "CUST-123",
      "items": [{"productId": "PROD-1", "quantity": 2}]
    },
    "protocol": "CM/1.0"
  }'

Best for: Modern microservices, REST APIs, applications requiring Spring ecosystem features (JPA, transactions, security, etc.).

📚 See complete sample application →


Servlet-Based Applications (Java EE/Jakarta EE)

Use this when: You have an existing application running on traditional application servers (Tomcat, Jetty, WildFly, WebLogic, WebSphere) and want to add CommandMosaic without migrating to Spring Boot. Perfect for:

1. Add Dependency

<dependency>
    <groupId>org.commandmosaic</groupId>
    <artifactId>commandmosaic-servlet</artifactId>
    <version>2.0.0</version>
</dependency>

2. Configure Servlet in web.xml

Add the CommandMosaic servlet to your web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <servlet>
        <servlet-name>CommandDispatcherServlet</servlet-name>
        <servlet-class>org.commandmosaic.http.servlet.CommandDispatcherServlet</servlet-class>
        
        <!-- Required: Root package for commands -->
        <init-param>
            <param-name>org.commandmosaic.http.servlet.CommandDispatcherServlet.rootPackage</param-name>
            <param-value>com.example.commands</param-value>
        </init-param>
        
        <!-- Optional: Command interceptors for security, logging, etc. -->
        <init-param>
            <param-name>org.commandmosaic.http.servlet.CommandDispatcherServlet.interceptors</param-name>
            <param-value>com.example.security.AuthInterceptor,com.example.logging.LoggingInterceptor</param-value>
        </init-param>
        
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>CommandDispatcherServlet</servlet-name>
        <url-pattern>/api/dispatch</url-pattern>
    </servlet-mapping>
</web-app>

3. Implement Your Commands

package com.example.commands;

import org.commandmosaic.api.Command;
import org.commandmosaic.api.CommandContext;
import org.commandmosaic.api.Parameter;

public class ProcessLegacyTransaction implements Command<TransactionResult> {
    
    @Parameter
    private String accountId;
    
    @Parameter
    private BigDecimal amount;
    
    @Override
    public TransactionResult execute(CommandContext context) {
        // Your business logic - can call EJBs, use JNDI lookups, etc.
        InitialContext ctx = new InitialContext();
        LegacyService service = (LegacyService) ctx.lookup("java:app/LegacyService");
        
        return service.processTransaction(accountId, amount);
    }
}

4. Call Your API

curl -X POST http://localhost:8080/yourapp/api/dispatch \
  -H "Content-Type: application/json" \
  -d '{
    "command": "ProcessLegacyTransaction",
    "parameters": {
      "accountId": "ACC-789",
      "amount": 150.00
    },
    "protocol": "CM/1.0"
  }'

Integration Strategy:

Best for: Modernizing legacy applications, adding new API endpoints to existing systems, gradual migration strategies.


AWS Lambda Functions

CommandMosaic is ideal for AWS Lambda because it minimizes infrastructure complexity: one Lambda function, one API Gateway endpoint, unlimited operations.

Benefits for Lambda

Option A: AWS Lambda with Spring Boot

Use this when: You want the full Spring ecosystem in Lambda (dependency injection, Spring Data, transactions, etc.).

1. Add Dependency
<dependency>
    <groupId>org.commandmosaic</groupId>
    <artifactId>commandmosaic-aws-lambda-springboot</artifactId>
    <version>2.0.0</version>
</dependency>
2. Create Lambda Request Handler
package com.example;

import org.commandmosaic.aws.lambda.springboot.SpringBootLambdaCommandDispatcherRequestHandler;

public class AppRequestHandler extends SpringBootLambdaCommandDispatcherRequestHandler {
    
    public AppRequestHandler() {
        super(Application.class); // Your @SpringBootApplication class
    }
}
3. Configure CommandMosaic in Spring
package com.example;

import org.commandmosaic.api.configuration.CommandDispatcherConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    @Bean
    public CommandDispatcherConfiguration commandDispatcherConfig() {
        return CommandDispatcherConfiguration.builder()
            .rootPackage("com.example.commands")
            .build();
    }
}
4. Implement Commands

Commands are Spring beans with full Spring support:

package com.example.commands;

import org.commandmosaic.api.Command;
import org.commandmosaic.api.CommandContext;
import org.commandmosaic.api.Parameter;
import org.springframework.beans.factory.annotation.Autowired;

public class ProcessOrder implements Command<OrderResult> {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private NotificationService notificationService;
    
    @Parameter
    private String orderId;
    
    @Override
    public OrderResult execute(CommandContext context) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
            
        order.process();
        orderRepository.save(order);
        notificationService.sendConfirmation(order);
        
        return new OrderResult(order);
    }
}

Important: Commands should NOT be annotated with @Component. They are automatically registered as prototype-scoped beans by CommandMosaic.

5. Module Configuration (Java 9+)

If using Java modules, add to module-info.java:

module com.example.app {
    requires org.commandmosaic.aws.lambda.springboot;
    requires spring.boot.autoconfigure;
    requires spring.boot;
    requires spring.context;
    requires spring.beans;

    opens com.example to
        spring.core, spring.context, spring.beans,
        org.commandmosaic.core;
}
6. Deploy to Lambda
  1. Package your application as a fat JAR
  2. Create Lambda function with handler: com.example.AppRequestHandler
  3. Configure API Gateway to proxy requests to Lambda
  4. Deploy

📚 See complete sample application →


Option B: AWS Lambda with Plain Java

Use this when: You want minimal dependencies and fastest cold starts without Spring overhead.

1. Add Dependency
<dependency>
    <groupId>org.commandmosaic</groupId>
    <artifactId>commandmosaic-aws-lambda-plain-java</artifactId>
    <version>2.0.0</version>
</dependency>
2. Create Lambda Request Handler
package com.example;

import org.commandmosaic.api.configuration.CommandDispatcherConfiguration;
import org.commandmosaic.aws.lambda.plain.PlainLambdaCommandDispatcherRequestHandler;

public class AppRequestHandler extends PlainLambdaCommandDispatcherRequestHandler {
    
    public AppRequestHandler() {
        super(CommandDispatcherConfiguration.builder()
            .rootPackage("com.example.commands")
            .build());
    }
}
3. Implement Commands
package com.example.commands;

import org.commandmosaic.api.Command;
import org.commandmosaic.api.CommandContext;
import org.commandmosaic.api.Parameter;

public class GetWeather implements Command<WeatherResult> {
    
    @Parameter
    private String city;
    
    @Override
    public WeatherResult execute(CommandContext context) {
        // Your business logic
        // Note: No Spring dependency injection, manage dependencies manually
        WeatherService service = new WeatherService();
        return service.getWeather(city);
    }
}
4. Module Configuration (Java 9+)
module com.example.app {
    requires org.commandmosaic.aws.lambda.plain;

    opens com.example to
        org.commandmosaic.core;
}
5. Deploy to Lambda

Same deployment process as Spring Boot version, but with faster cold starts and smaller package size.

Best for: Serverless architectures, minimizing AWS configuration, rapid feature deployment, cost optimization.


Testing Your Commands

One of CommandMosaic’s key benefits is testability. Commands are plain Java objects that can be tested without any container:

@Test
public void testPaymentProcessing() {
    // Arrange
    ProcessPayment command = new ProcessPayment();
    command.setOrderId("ORD-123");
    command.setAmount(new BigDecimal("99.99"));
    
    // Act
    PaymentResult result = command.execute(null);
    
    // Assert
    assertNotNull(result.getTransactionId());
    assertEquals("SUCCESS", result.getStatus());
}

For integration testing with Spring:

@SpringBootTest
public class CommandIntegrationTest {
    
    @Autowired
    private CommandDispatcher dispatcher;
    
    @Test
    @Transactional
    public void testCreateOrderWithDatabase() {
        CreateOrder command = new CreateOrder();
        command.setCustomerId("CUST-123");
        command.setItems(Arrays.asList(new OrderItem("PROD-1", 2)));
        
        OrderResult result = dispatcher.dispatchCommand(command, null);
        
        assertNotNull(result.getOrderId());
    }
}

Lambda functions can be tested the same way - no AWS-specific tooling required!


Security

Security

CommandMosaic provides declarative, annotation-based security that works consistently across all runtime environments.

Securing Commands with Annotations

Control access at the command level using simple annotations:

Public Access

import org.commandmosaic.security.Access;

@Access.IsPublic
public class GetProductCatalog implements Command<List<Product>> {
    @Parameter
    private String category;
    
    @Override
    public List<Product> execute(CommandContext context) {
        // Anyone can access this command
        return productService.getProducts(category);
    }
}

Authenticated Access

@Access.RequiresAuthentication  // Any authenticated user
public class UpdateProfile implements Command<Void> {
    @Parameter
    private String email;
    
    @Parameter
    private String phoneNumber;
    
    @Override
    public Void execute(CommandContext context) {
        // Only authenticated users can access
        String userId = context.getUserId();
        userService.updateProfile(userId, email, phoneNumber);
        return null;
    }
}

Role-Based Access

@Access.RequiresAnyOfTheAuthorities({"ADMIN", "MANAGER"})
public class DeleteUser implements Command<Void> {
    @Parameter
    private String userId;
    
    @Override
    public Void execute(CommandContext context) {
        // Only ADMIN or MANAGER roles can access
        userService.deleteUser(userId);
        return null;
    }
}

Implementing Authentication

Create a security interceptor by extending DefaultSecurityCommandInterceptor:

package com.example.security;

import org.commandmosaic.api.CommandContext;
import org.commandmosaic.security.AuthenticationException;
import org.commandmosaic.security.interceptor.DefaultSecurityCommandInterceptor;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JwtSecurityInterceptor extends DefaultSecurityCommandInterceptor {
    
    private final JwtService jwtService;
    
    public JwtSecurityInterceptor() {
        this.jwtService = new JwtService();
    }
    
    @Override
    protected Set<String> attemptLogin(CommandContext context) 
            throws AuthenticationException {
        // Extract authentication data from request
        Map<String, Object> auth = context.getAuth();
        if (auth == null || !auth.containsKey("token")) {
            throw new AuthenticationException("No authentication token provided");
        }
        
        String token = (String) auth.get("token");
        
        try {
            // Validate token and extract user info
            Claims claims = jwtService.validateToken(token);
            
            // Extract roles/authorities from token
            List<String> roles = claims.get("roles", List.class);
            
            return new HashSet<>(roles);
            
        } catch (JwtException e) {
            throw new AuthenticationException("Invalid token: " + e.getMessage());
        }
    }
}

Registering the Security Interceptor

Plain Java / Plain Lambda

CommandDispatcherConfiguration config = CommandDispatcherConfiguration.builder()
    .rootPackage("com.example.commands")
    .interceptor(JwtSecurityInterceptor.class)
    .build();

Spring Boot / Spring Boot Lambda

@Configuration
public class SecurityConfig {
    
    @Bean
    public JwtSecurityInterceptor jwtSecurityInterceptor() {
        return new JwtSecurityInterceptor();
    }
    
    @Bean
    public CommandDispatcherConfiguration commandDispatcherConfig() {
        return CommandDispatcherConfiguration.builder()
            .rootPackage("com.example.commands")
            .interceptor(JwtSecurityInterceptor.class)
            .build();
    }
}

Servlet (web.xml)

<servlet>
    <servlet-name>CommandDispatcherServlet</servlet-name>
    <servlet-class>org.commandmosaic.http.servlet.CommandDispatcherServlet</servlet-class>
    
    <init-param>
        <param-name>org.commandmosaic.http.servlet.CommandDispatcherServlet.rootPackage</param-name>
        <param-value>com.example.commands</param-value>
    </init-param>
    
    <init-param>
        <param-name>org.commandmosaic.http.servlet.CommandDispatcherServlet.interceptors</param-name>
        <param-value>com.example.security.JwtSecurityInterceptor</param-value>
    </init-param>
</servlet>

Client Authentication

Clients pass authentication data in the auth field of the request:

{
  "command": "DeleteUser",
  "parameters": {
    "userId": "USER-123"
  },
  "auth": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  },
  "protocol": "CM/1.0"
}

The auth field accepts any key-value pairs. Common patterns:

JWT Token:

"auth": {
  "token": "eyJhbGc..."
}

Username/Password:

"auth": {
  "username": "john.doe",
  "password": "secret123"
}

API Key:

"auth": {
  "apiKey": "ak_live_123456789"
}

Multiple credentials:

"auth": {
  "apiKey": "ak_live_123456789",
  "deviceId": "device-xyz",
  "sessionToken": "sess_abc123"
}

Security Flow

  1. Client sends request with auth data
  2. CommandMosaic calls your attemptLogin() method
  3. Your interceptor validates credentials and returns roles
  4. CommandMosaic checks if returned roles match command’s @Access annotation
  5. If authorized, command executes; otherwise, AuthorizationException is thrown

Integration with Spring Security (Optional)

For Spring Boot applications, you can integrate with Spring Security:

@Component
public class SpringSecurityInterceptor extends DefaultSecurityCommandInterceptor {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Override
    protected Set<String> attemptLogin(CommandContext context) 
            throws AuthenticationException {
        Map<String, Object> auth = context.getAuth();
        String token = (String) auth.get("token");
        
        // Use Spring Security's authentication
        Authentication authentication = 
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(token, null)
            );
        
        return authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toSet());
    }
}

Advanced Topics

Why Commands Should NOT Be Annotated with @Component

Important Design Principle: Commands in Spring-based integrations should never be annotated with @Component, @Service, or any other stereotype annotation that would register them as Spring beans.

The Problem

Commands have @Parameter fields that receive different values for each request:

public class ProcessPayment implements Command<PaymentResult> {
    @Parameter
    private String orderId;    // Different for each request!
    
    @Parameter
    private BigDecimal amount;  // Different for each request!
    
    // ...
}

If you annotate this with @Component:

How CommandMosaic Solves This

The SpringCommandExecutor automatically registers commands as prototype-scoped beans:

// From SpringCommandExecutor.java
if (!beanFactory.containsBeanDefinition(commandClassName)) {
    GenericBeanDefinition gbd = new GenericBeanDefinition();
    gbd.setBeanClass(commandClass);
    gbd.setScope(BeanDefinition.SCOPE_PROTOTYPE);  // ← Key: Fresh instance per request
    beanFactory.registerBeanDefinition(commandClassName, gbd);
}

For each command execution:

  1. A new command instance is created (prototype scope)
  2. Spring injects dependencies (@Autowired services)
  3. CommandMosaic injects parameters (@Parameter fields)
  4. The command executes with isolated state
  5. The instance is discarded after execution

What You CAN Inject

Commands can use @Autowired to inject singleton services:

public class CreateOrder implements Command<OrderResult> {
    
    @Autowired
    private OrderService orderService;        // ✓ Singleton service - safe to inject
    
    @Autowired
    private PaymentService paymentService;    // ✓ Singleton service - safe to inject
    
    @Parameter
    private String customerId;                 // ✓ Per-request parameter
    
    @Override
    @Transactional  // ✓ Spring AOP works correctly
    public OrderResult execute(CommandContext context) {
        // Each execution gets a fresh command instance with its own parameters
        // but shares the same service instances (which are stateless singletons)
    }
}

Summary

Performance Implications

** Command objects are created as prototype-scoped beans: These have overhead compared to singletons**, but the impact is often negligible for typical use cases:

The Cost:

Typical Performance Impact:

When This Matters:

Performance vs. Correctness Trade-off:

The prototype scope is required for correctness. Without it:

// ❌ Singleton command = DATA CORRUPTION
@Component  // Creates singleton
public class ProcessPayment implements Command<PaymentResult> {
    @Parameter
    private String orderId;  // Shared across threads!
    
    // Thread 1: orderId = "A"
    // Thread 2: orderId = "B" (overwrites Thread 1's value!)
    // Result: Thread 1 processes order "B" instead of "A"
}

The performance overhead is far preferable to data corruption.

Optimization Strategies (if needed):

  1. Most commands don’t need optimization - I/O dominates execution time

  2. For high-throughput simple commands, consider:
    // Keep business logic services as singletons
    @Service
    public class PaymentCalculator {
        public BigDecimal calculate(Order order) {
            // Complex calculation logic as singleton
            // No state, thread-safe
        }
    }
       
    // Command is just a thin wrapper
    public class CalculatePayment implements Command<BigDecimal> {
        @Autowired
        private PaymentCalculator calculator;  // Singleton, no creation overhead
           
        @Parameter
        private Order order;
           
        @Override
        public BigDecimal execute(CommandContext context) {
            return calculator.calculate(order);  // Delegate to singleton
        }
    }
    
  3. For tight-loop scenarios, invoke services directly:
    @Service
    public class OrderProcessor {
        @Autowired
        private PaymentService paymentService;
           
        public void processBatch(List<Order> orders) {
            for (Order order : orders) {
                // Don't dispatch commands in a tight loop
                // Call services directly for batch operations
                paymentService.process(order);
            }
        }
    }
    
  4. Profile before optimizing - Use tools like JProfiler or YourKit to identify actual bottlenecks

Real-World Perspective:

In a typical web application:

The bean creation overhead is 0.1-2% of total request time - usually not worth optimizing.

Conclusion: The prototype scope overhead is a reasonable trade-off for thread-safety and correctness. For the vast majority of use cases (HTTP APIs, REST services, Lambda functions), the performance impact is negligible compared to I/O operations.

Using CommandDispatcherServer

For custom integrations, use CommandDispatcherServer to handle HTTP-style requests:

import org.commandmosaic.api.server.CommandDispatcherServer;
import org.commandmosaic.core.server.DefaultCommandDispatcherServer;

CommandDispatcher dispatcher = // ... create dispatcher
CommandDispatcherServer server = new DefaultCommandDispatcherServer(dispatcher);

// Read from InputStream, write to OutputStream
server.serviceRequest(inputStream, outputStream);

This abstraction allows integration with any framework that provides request/response streams (Netty, Vert.x, etc.).

Command Interceptors

Create custom interceptors for cross-cutting concerns:

public class LoggingInterceptor implements CommandInterceptor {
    
    @Override
    public <T> T intercept(Command<T> command, CommandContext context, 
                           InterceptorChain<T> chain) throws Exception {
        long start = System.currentTimeMillis();
        
        try {
            T result = chain.proceed(command, context);
            long duration = System.currentTimeMillis() - start;
            log.info("Command {} executed in {}ms", 
                     command.getClass().getSimpleName(), duration);
            return result;
        } catch (Exception e) {
            log.error("Command {} failed", command.getClass().getSimpleName(), e);
            throw e;
        }
    }
}

Register multiple interceptors:

CommandDispatcherConfiguration config = CommandDispatcherConfiguration.builder()
    .rootPackage("com.example.commands")
    .interceptor(LoggingInterceptor.class)
    .interceptor(MetricsInterceptor.class)
    .interceptor(SecurityInterceptor.class)
    .build();

Command Discovery

Commands are discovered by package scanning. The command name is derived from:

Option 1: Simple class name

package com.example.commands;

public class ProcessPayment implements Command<PaymentResult> {
    // Invoked as: "ProcessPayment"
}

Option 2: Full package path

package com.example.commands.payments;

public class ProcessRefund implements Command<RefundResult> {
    // Can be invoked as: "payments/ProcessRefund"
}

Module System Support (Java 9+)

When using Java Platform Module System, ensure your module opens packages to CommandMosaic:

module com.example.app {
    requires org.commandmosaic.spring.boot.autoconfigure;
    requires spring.boot;
    requires spring.context;
    
    // Open command packages for reflection
    opens com.example.commands to org.commandmosaic.core;
}

Sample Applications

Complete working examples are available:

Sample Description Link
Hello World Plain Java command pattern View →
Spring Boot Web App REST API with Spring Boot View →
AWS Lambda Serverless with Spring Boot View →

Running Sample Applications

To run sample applications:

  1. Download the sample project
  2. Open the pom.xml file
  3. Remove the <parent>...</parent> section
  4. Uncomment the sections marked for standalone use
  5. Adjust groupId/artifactId as needed
  6. Run mvn clean install

Dependency Reference

Choose one dependency based on your use case:

Use Case Artifact ID Version
Plain Java (no web) commandmosaic-plain-java 2.0.0
Spring Framework commandmosaic-spring 2.0.0
Spring Boot commandmosaic-spring-boot-autoconfigure 2.0.0
Servlet (Java EE/Jakarta EE) commandmosaic-servlet 2.0.0
AWS Lambda (Plain Java) commandmosaic-aws-lambda-plain-java 2.0.0
AWS Lambda (Spring Boot) commandmosaic-aws-lambda-springboot 2.0.0

Maven example:

<dependency>
    <groupId>org.commandmosaic</groupId>
    <artifactId>commandmosaic-spring-boot-autoconfigure</artifactId>
    <version>2.0.0</version>
</dependency>

Spring Boot Version Requirements

CommandMosaic requires Spring Boot 2.x or later. Spring Boot 1.x is not supported (as it has been deprecated by Pivotal).


Architecture Considerations

Monolithic vs. Microservices

CommandMosaic applications can be considered “monolithic” in that all commands live in one codebase. However, this approach offers several advantages:

Benefits:

Trade-offs:

When this works well:

When to use separate services:

Lambda Considerations

For AWS Lambda, CommandMosaic’s single-function approach differs from the microservices ideal of “one function per operation.” Consider:

Advantages:

Trade-offs:

Best practices:


Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues on GitHub.

License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.