Frameworks and Drivers: The Outermost Layer

Learning Objectives: By the end of this lesson, you will be able to:

  • Identify the role of frameworks and drivers in Clean Architecture
  • Understand how to keep frameworks and external tools isolated from core logic
  • Integrate frameworks (UI, DB, web, devices) with Clean Architecture in Java

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

Have you ever had to upgrade or swap out a major framework (like a web server or database) in a project? What challenges did you face? How would it help if your business logic was untouched by these changes?

Lesson Overview

  • Duration: 90 minutes
  • Prerequisites: Understanding of Clean Architecture layers, interface adapters, and dependency inversion
  • Materials Needed: Java development environment, example frameworks (e.g., Spring, JDBC), pen and paper

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. What are frameworks and drivers?
  2. How to keep frameworks isolated from core logic
  3. Integrating frameworks with Clean Architecture in Java
  4. Hands-on practice and exercises
  5. Common pitfalls and how to avoid them

Success Criteria:

By the end of this session, you should be able to connect frameworks to your application without polluting your core logic.

3. Foundation Knowledge (15-20 minutes)

Core Concepts

What are Frameworks and Drivers?

  • The outermost layer of Clean Architecture
  • Includes web frameworks (Spring, Jakarta EE), UI frameworks (JavaFX, Swing), database drivers (JDBC, Hibernate), and external libraries
  • These are “details” that should not leak into your business logic

Real-World Analogy: Think of a universal remote: it can control any TV, but the TV’s core functions don’t change based on the remote. The remote (framework) is replaceable.

Why This Matters: Frameworks and drivers change frequently. By isolating them, you make your application more maintainable and adaptable.

Glue Code and Dependency Injection

  • Glue code wires together the core logic and frameworks
  • Dependency Injection (DI) containers (like Spring) are often configured here
  • The goal is to keep all framework-specific code at the edges

Key Terminology

  • Framework: A reusable set of libraries or tools for building applications
  • Driver: A library that enables communication with external systems (e.g., databases)
  • Glue Code: Code that connects different parts of the system, especially at boundaries

4. Detailed Explanation and Examples (25-30 minutes)

Understanding Frameworks and Drivers in Clean Architecture

Frameworks and drivers should be replaceable. The core logic should not depend on them, but they can depend on the core logic.

Practical Applications

  • Using Spring Boot for web APIs, but keeping controllers thin and delegating to use cases
  • Swapping JDBC for Hibernate without changing business logic
  • Integrating a new UI framework (e.g., JavaFX) with the same core logic

Common Misconceptions

  1. Misconception: It’s okay for business logic to import framework classes. Reality: This creates tight coupling and makes upgrades difficult.
  2. Misconception: Dependency Injection must be used everywhere. Reality: Use DI to wire dependencies, but keep it out of core logic.

5. Live Coding Demonstration (20-25 minutes)

Example 1: Spring Boot Controller Wiring to Use Case

// Spring Boot controller in the Frameworks & Drivers layer
import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;

@RestController
@RequestMapping("/tasks")
public class TaskRestController {
    private final AddTaskUseCase addTaskUseCase;
    private final AddTaskPresenter presenter;

    @Autowired
    public TaskRestController(AddTaskUseCase addTaskUseCase, AddTaskPresenter presenter) {
        this.addTaskUseCase = addTaskUseCase;
        this.presenter = presenter;
    }

    @PostMapping
    public String addTask(@RequestBody AddTaskRequest request) {
        AddTaskResponse response = addTaskUseCase.execute(request);
        return presenter.present(response);
    }
}

Explanation:

  • The controller is in the outer layer and depends on the use case and presenter
  • No business logic or data access code in the controller
  • The use case and presenter are injected (wired) by the framework

Example 2: Database Configuration with JDBC

// Framework-specific configuration (JDBC)
import java.sql.Connection;
import java.sql.DriverManager;

public class DatabaseConfig {
    public static Connection getConnection() throws Exception {
        return DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/mydb", "user", "password");
    }
}

Key Points:

  • All JDBC code is isolated in the outer layer
  • The repository adapter uses this configuration, but the use case is unaware of it

Example 3: Dependency Injection Wiring

// Manual wiring (without a DI framework)
public class Main {
    public static void main(String[] args) {
        TaskRepository repository = new InMemoryTaskRepository();
        AddTaskUseCase useCase = new AddTaskUseCase(repository);
        AddTaskPresenter presenter = new AddTaskPresenter();
        TaskRestController controller = new TaskRestController(useCase, presenter);
        // Start web server, etc.
    }
}

Debugging Demonstration

Show a common error:

// Common mistake: Business logic imports framework classes
import org.springframework.web.bind.annotation.*; // in a use case or entity (should not happen)

public class AddTaskUseCase {
    // ...
}

// Corrected version: No framework imports in core logic
public class AddTaskUseCase {
    // ...
}

6. Hands-On Practice (30-40 minutes)

Exercise 1: Basic Practice (10 minutes)

Objective: Wire a use case and presenter to a framework controller.

Instructions:

  1. Create a simple controller class (e.g., BlogPostRestController) in the frameworks layer.
  2. Inject a use case and presenter.
  3. Delegate HTTP requests to the use case and format the response with the presenter.

Starter Code:

public class BlogPostRestController {
    // ...
}

Solution:

public class BlogPostRestController {
    private final CreateBlogPostUseCase useCase;
    private final CreateBlogPostPresenter presenter;
    public BlogPostRestController(CreateBlogPostUseCase useCase, CreateBlogPostPresenter presenter) {
        this.useCase = useCase;
        this.presenter = presenter;
    }
    public String createPost(CreateBlogPostRequest request) {
        CreateBlogPostResponse response = useCase.execute(request);
        return presenter.present(response);
    }
}

Exercise 2: Intermediate Challenge (15 minutes)

Objective: Isolate database configuration from business logic.

Requirements:

  • Create a configuration class for database connections
  • Ensure repositories use this configuration, not the use case
  • Demonstrate how to swap database implementations

Hints:

  • Use dependency injection or factory methods

Exercise 3: Creative Application (15 minutes)

Objective: Integrate a new UI or API framework with your core logic.

Challenge: Design a controller or UI class for a library system that delegates to use cases and presenters. Ensure no business logic or data access code is present in the controller/UI.

Extension Ideas:

  • Add error handling and user-friendly messages
  • Demonstrate how to swap frameworks (e.g., from console to web)

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Mistakes

  1. Problem: Business logic imports framework classes Cause: Mixing concerns Solution: Keep all framework code in the outer layer

  2. Problem: Framework configuration leaks into core logic Cause: Poor separation of concerns Solution: Isolate configuration and glue code

Debugging Tips

  • Check imports in core logic for framework classes
  • Use dependency injection to wire dependencies
  • Write integration tests for framework adapters

8. Best Practices and Professional Tips (5 minutes)

Industry Standards

  • Keep frameworks and drivers at the edges of your system
  • Use dependency injection frameworks for wiring
  • Organize code by business domain, not by framework

Next Steps in Your Learning Journey

  • Try swapping frameworks in a small project
  • Explore advanced DI frameworks (e.g., Spring, Guice)
  • Read about the Adapter and Dependency Injection patterns

9. Summary and Review (5 minutes)

Key Takeaways

  1. Frameworks and drivers are details, not core logic
  2. Keep all framework-specific code in the outer layer
  3. Use dependency injection and glue code to wire your application

Quick Review Questions

  1. What is the role of frameworks and drivers in Clean Architecture?
  2. Why should business logic not import framework classes?
  3. How does dependency injection help keep your architecture clean?

For Instructors

Teaching Tips

  • Use real-world framework integration scenarios
  • Encourage students to refactor code to isolate frameworks
  • Review controller and configuration code for separation of concerns

Assessment Ideas

  • Ask students to wire up a framework controller to use cases and presenters
  • Review code for correct use of dependency injection
  • Peer review of framework adapter implementations

Adaptation Notes

  • For beginners, provide more scaffolding and examples
  • For advanced students, challenge them to swap frameworks or databases
  • Adjust timing based on class experience and engagement