Reading.CleanArchitecture.FrameworksAreDetails

In this chapter, Robert C. Martin emphasizes that frameworks, while powerful tools, should be treated as implementation details rather than foundational elements of a system’s architecture. Over-reliance on frameworks can lead to tightly coupled, inflexible systems that are hard to maintain or adapt. Instead, frameworks should be isolated to specific parts of the system, allowing the core business logic to remain independent.

Key Concepts of Chapter 32

1. Frameworks as Tools, Not Foundations

  • Frameworks (e.g., Spring, Django, Rails) are useful but should not dictate the structure of your application.
  • Your system’s architecture should focus on business rules and use cases, not the requirements of a particular framework.

Core Idea:

  • “The architecture of a system is defined by its use cases and business rules, not by the framework used to deliver them.”

2. Framework Independence

  • Dependence on a framework can:
    • Tie your application to a specific technology.
    • Make it harder to adapt to new requirements or replace the framework in the future.
  • By keeping the framework isolated, you can more easily:
    • Switch to a different framework.
    • Use multiple frameworks for different delivery mechanisms (e.g., web, mobile).

3. The Dependency Rule

  • Framework-specific code should reside in the outermost layer of your architecture (e.g., the frameworks and drivers layer).
  • The core logic (business rules and use cases) must not depend on the framework.

Example:

  • Instead of calling a framework’s ORM (e.g., Hibernate) directly from your business logic, abstract database operations behind a repository interface.

4. Treating Frameworks as Plugins

  • Frameworks should be treated as plugins that can be swapped out or replaced without affecting the core logic.
  • Framework-specific functionality (e.g., routing, dependency injection, database access) should be confined to adapters and controllers.

5. Examples of Framework Isolation

a. Web Frameworks:
  • Controllers handle HTTP requests and delegate to use cases, keeping the core logic independent of the web framework.

Example:

@RestController
public class UserController {
    private final GetUserUseCase getUserUseCase;

    public UserController(GetUserUseCase getUserUseCase) {
        this.getUserUseCase = getUserUseCase;
    }

    @GetMapping("/users/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable int id) {
        User user = getUserUseCase.execute(id);
        return ResponseEntity.ok(new UserResponse(user));
    }
}
b. Database Frameworks (ORM):
  • Use repositories to abstract ORM-specific logic, keeping the core logic independent.

Example:

public interface UserRepository {
    User findById(int id);
    void save(User user);
}

public class JpaUserRepository implements UserRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public User findById(int id) {
        return entityManager.find(User.class, id);
    }

    @Override
    public void save(User user) {
        entityManager.persist(user);
    }
}
c. Dependency Injection Frameworks:
  • Use the framework’s DI (Dependency Injection) capabilities only for wiring dependencies, not within the core logic.

6. Testing Without Frameworks

  • Isolating frameworks allows you to test core business logic without depending on the framework’s setup.
  • For example, you can test use cases and entities independently of Spring Boot or Django.

Example: Testing a Use Case:

@Test
public void testGetUser() {
    UserRepository mockRepository = Mockito.mock(UserRepository.class);
    GetUserUseCase useCase = new GetUserUseCase(mockRepository);

    User mockUser = new User(1, "John Doe");
    Mockito.when(mockRepository.findById(1)).thenReturn(mockUser);

    User user = useCase.execute(1);
    assertEquals("John Doe", user.getName());
}

7. Common Pitfalls with Frameworks

a. Letting the Framework Dictate Architecture:
  • Many developers follow the default structure of a framework, which can lead to tight coupling.
  • Example: Using the MVC pattern imposed by a web framework to structure all layers of the application.
b. Framework-Specific Business Logic:
  • Embedding business rules into framework-specific components (e.g., controllers or database queries) makes the logic difficult to test and maintain.
c. Framework Lock-In:
  • Over-reliance on framework-specific features (e.g., ORM, session management) can make it hard to switch frameworks.

8. Benefits of Treating Frameworks as Details

a. Flexibility:
  • You can switch frameworks or use different frameworks for different delivery mechanisms without affecting core logic.
b. Testability:
  • Core logic can be tested in isolation, without requiring the framework to be loaded.
c. Maintainability:
  • Changes in framework versions or technologies don’t ripple through the entire system.
d. Reusability:
  • The same core logic can be reused across different platforms or frameworks.

Practical Example: E-Commerce System

Core Logic (Use Case):

public class PlaceOrderUseCase {
    private final OrderRepository orderRepository;

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

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

Framework Layer (Controller):

@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.toDomain();
        placeOrderUseCase.execute(order);
        return ResponseEntity.ok("Order placed successfully");
    }
}

Key Takeaways

  1. Frameworks Are Tools:
    • Use frameworks to deliver functionality but keep them out of the core business logic.
  2. Follow the Dependency Rule:
    • Core logic should never depend on framework-specific components.
  3. Treat Frameworks as Plugins:
    • Isolate framework-specific code in the outer layers of your architecture.
  4. Test Core Logic Without Frameworks:
    • Ensure core logic is framework-agnostic, enabling independent testing.
This entry was posted in Без рубрики. Bookmark the permalink.