Reading.CleanArchitecture.WebIsADetail

1. The Web Is Not the System

  • The web is simply a delivery mechanism for the system’s functionality.
  • The system’s core business rules and use cases should not depend on HTTP, URLs, or web technologies.

Core Idea:

  • Treat the web as just one of many potential interfaces for interacting with the system (e.g., web, CLI, mobile app).

2. Core Logic Independence

  • Core logic should not be tied to web-specific details like:
    • HTTP requests or responses.
    • URL routing.
    • Session or cookie handling.
  • Instead, the web layer acts as an adapter that translates HTTP data into a format usable by the core logic.

Example:

  • Instead of directly using HttpRequest in a use case, pass an abstracted Input object.

3. The Role of Controllers

  • Controllers belong to the interface adapter layer and act as intermediaries between the web and the core application.
  • Responsibilities of controllers:
    • Parse incoming HTTP requests.
    • Call appropriate use cases with translated inputs.
    • Format use case outputs into HTTP responses.

Example:

@RestController
public class OrderController {
    private final PlaceOrderUseCase placeOrderUseCase;

    public OrderController(PlaceOrderUseCase placeOrderUseCase) {
        this.placeOrderUseCase = placeOrderUseCase;
    }

    @PostMapping("/orders")
    public ResponseEntity<String> placeOrder(@RequestBody OrderRequest request) {
        placeOrderUseCase.execute(request.toDomainOrder());
        return ResponseEntity.ok("Order placed successfully");
    }
}

4. Use Cases Remain Web-Agnostic

  • The use case layer contains application-specific workflows that are independent of the web.
  • Example:
    • A PlaceOrderUseCase should handle the business logic of placing an order without knowing whether it was triggered by a web request, a CLI command, or a scheduled job.

Use Case Example:

public class PlaceOrderUseCase {
    private final OrderRepository orderRepository;

    public PlaceOrderUseCase(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void execute(Order order) {
        if (order.isValid()) {
            orderRepository.save(order);
        } else {
            throw new IllegalArgumentException("Invalid order");
        }
    }
}

5. Framework Independence

  • Web frameworks (e.g., Spring, Django, Express) should not dictate the architecture of the system.
  • Framework-specific elements should be confined to the outermost layer (e.g., controllers or route handlers).
  • This ensures that the system can switch frameworks or delivery mechanisms with minimal changes.

6. Testing the Core Without the Web

  • Since the core logic is independent of the web, you can test it without needing a web server.
  • Controllers can be tested separately by mocking the use cases they interact with.

Example: Testing a Use Case

@Test
public void testPlaceOrderUseCase() {
    OrderRepository mockRepository = Mockito.mock(OrderRepository.class);
    PlaceOrderUseCase useCase = new PlaceOrderUseCase(mockRepository);

    Order order = new Order(...);
    useCase.execute(order);

    Mockito.verify(mockRepository).save(order);
}

7. The Benefits of Treating the Web as a Detail

a. Flexibility:
  • The same core logic can support multiple delivery mechanisms (e.g., web, mobile app, CLI).
b. Testability:
  • Core logic can be tested in isolation, without needing a running web server or network.
c. Maintainability:
  • Changes in web technology (e.g., migrating from Spring to Micronaut) don’t affect the core business logic.
d. Reusability:
  • The core logic can be reused across different projects or interfaces.

Practical Example: E-Commerce System

1. Core Logic (Use Case):

  • Business rules and workflows remain agnostic of the web.
public class PlaceOrderUseCase {
    private final OrderRepository orderRepository;

    public PlaceOrderUseCase(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void execute(Order order) {
        orderRepository.save(order);
    }
}

2. Web Layer (Controller):

  • Controllers handle web-specific details and delegate to the use case.

2. Web Layer (Controller):

  • Controllers handle web-specific details and delegate to the use case.
@RestController
public class OrderController {
    private final PlaceOrderUseCase placeOrderUseCase;

    public OrderController(PlaceOrderUseCase placeOrderUseCase) {
        this.placeOrderUseCase = placeOrderUseCase;
    }

    @PostMapping("/orders")
    public ResponseEntity<String> placeOrder(@RequestBody OrderRequest request) {
        Order order = request.toDomainOrder();
        placeOrderUseCase.execute(order);
        return ResponseEntity.ok("Order placed successfully");
    }
}

3. Testing Without the Web:

  • The use case is tested independently of the web framework.
@Test
public void testPlaceOrder() {
    OrderRepository repository = new InMemoryOrderRepository();
    PlaceOrderUseCase useCase = new PlaceOrderUseCase(repository);

    Order order = new Order();
    useCase.execute(order);

    assertNotNull(repository.findById(order.getId()));
}

Key Takeaways

  1. The Web Is a Delivery Mechanism:
    • Treat the web as a detail, not the foundation of your architecture.
  2. Core Logic Should Be Web-Agnostic:
    • Use cases and business rules should not depend on HTTP, URLs, or web technologies.
  3. Controllers Translate Data:
    • Controllers handle the web-specific logic, delegating business workflows to use cases.
  4. Framework Independence:
    • Keep frameworks confined to the outermost layer to enable flexibility and maintainability.
  5. Test Core Logic in Isolation:
    • Decoupling the web layer allows for efficient and focused testing of business logic.
This entry was posted in Без рубрики. Bookmark the permalink.