Backend Development 10 min read

Eliminate Manual Controllers: Auto‑Expose Command & Query Services with Lego‑Starter

This guide shows how to eliminate manual Controller code by automatically exposing CommandService and QueryService as web endpoints, integrating with Swagger for dynamic API documentation, and explains the underlying unified controller architecture and extension points.

macrozheng
macrozheng
macrozheng
Eliminate Manual Controllers: Auto‑Expose Command & Query Services with Lego‑Starter

1. Overview

In daily development, writing Controllers is tedious; many companies forbid business logic in Controllers, yet source code often contains it. The author argues that Controllers are unnecessary.

1.1 Background

By encapsulating CommandService and QueryService, the author built application services, then wrote Controllers to expose them, which is repetitive. The strategy is to let the framework handle this repetitive work.

1.2 Goal

The goal is to avoid writing Controllers while preserving their effect.

Expose CommandService and QueryService directly as web endpoints without writing Controller code.

Integrate with Swagger to dynamically generate API documentation for front‑end consumption.

2. Quick Start

2.1 Environment Preparation

Add the

lego-starter

dependency to the Maven

pom.xml

:

<code>&lt;dependency&gt;
    &lt;groupId&gt;com.geekhalo.lego&lt;/groupId&gt;
    &lt;artifactId&gt;lego-starter&lt;/artifactId&gt;
    &lt;version&gt;0.1.11-rest-SNAPSHOT&lt;/version&gt;
&lt;/dependency&gt;</code>

Add Swagger related dependencies:

<code>&lt;dependency&gt;
    &lt;groupId&gt;io.springfox&lt;/groupId&gt;
    &lt;artifactId&gt;springfox-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;3.0.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;io.springfox&lt;/groupId&gt;
    &lt;artifactId&gt;springfox-data-rest&lt;/artifactId&gt;
    &lt;version&gt;3.0.0&lt;/version&gt;
&lt;/dependency&gt;</code>

Create a

SpringFoxConfiguration

class to enable Swagger:

<code>@Configuration
@EnableSwagger2
public class SpringFoxConfiguration {
}
</code>

2.2 First Look at Unified Controllers

Open

http://127.0.0.1:8080/swagger-ui/

and you will see two generated Controllers:

command-dispatcher-controller : exposes

CommandService

for write/update operations, supporting both RequestBody and RequestParam.

query-dispatcher-controller : exposes

QueryService

for read operations, also supporting RequestBody and RequestParam, with additional support for HTTP GET.

2.3 Command Controller

2.3.1 Enable Command Controller

Annotate the

OrderCommandService

interface with

@AutoRegisterWebController

to expose it as a web endpoint:

<code>@CommandServiceDefinition(
        domainClass = Order.class,
        idClass = Long.class,
        repositoryClass = OrderRepository.class)
@AutoRegisterWebController(name = "order")
public interface OrderCommandService {
    void cancel(Long orderId);
    Long create(CreateOrderCommand command);
    void paySuccess(PaySuccessCommand command);
}
</code>

2.3.2 Use Order Command Controller

Visit the Swagger UI again; a new group of endpoints appears for the command methods.

All methods defined in

OrderCommandService

are automatically exposed as REST endpoints, with both body‑based and param‑based variants.

2.4 Query Controller

2.4.1 Enable Query Controller

Annotate the

OrderQueryService

interface similarly:

<code>@QueryServiceDefinition(domainClass = Order.class,
        repositoryClass = OrderQueryRepository.class)
@Validated
@AutoRegisterWebController(name = "order")
public interface OrderQueryService {
    OrderDetail getById(@Valid @NotNull(message = "订单号不能为null") Long id);
    Page&lt;OrderDetail&gt; pageByUserId(@Valid @NotNull(message = "查询参数不能为 null") PageByUserId query);
    List&lt;OrderDetail&gt; getByUserId(@Valid @NotNull(message = "查询参数不能为 null") GetByUserId getByUserId);
    Long countByUser(@Valid @NotNull(message = "查询参数不能为 null") CountByUserId countByUserId);
    List&lt;OrderDetail&gt; getPaidByUserId(Long id);
}
</code>

2.4.2 Use Order Query Controller

After refreshing Swagger UI, another set of endpoints appears for the query methods.

The request/response structures are clear, and the generated endpoints behave the same as hand‑written Controllers.

3. Design & Extension

The design consists of two parts:

Provide a unified Controller that acts as a dispatcher for all requests.

Integrate a plugin with Swagger to generate complete API documentation.

3.1 Unified Controller

The

QueryDispatcherController

serves as the entry point for all query requests. The core architecture is illustrated below:

Initialization flow:

Spring instantiates all

QueryService

beans.

Each instance is automatically registered in

QueryServicesRegistry

.

QueryMethodRegistry

parses methods from the registry and registers them.

Execution flow:

The client sends a request.

The request is routed to the appropriate method of

QueryDispatcherController

.

The controller retrieves the corresponding

QueryMethod

from

QueryMethodRegistry

using

serviceName

and

methodName

, invokes the business method, and returns the result.

3.2 Swagger Integration

QueryServicesProvider

works with Swagger to generate full API docs. The overall design is shown below:

Core process:

QueryServicesProvider

obtains

QueryMethod

information from

QueryMethodRegistry

.

It creates

RequestHandler

objects and registers them with Swagger.

When a user accesses Swagger UI, the handlers are converted into API documentation.

4. Project Information

Repository: https://gitee.com/litao851025/lego

Documentation: https://gitee.com/litao851025/lego/wikis/support/web

JavaSpring BootAPI DocumentationSwaggerQuery ServiceCommand ServiceUnified Controller
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.