Use Cases and Interactors: Orchestrating Application Business Rules

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

  • Explain the role of use cases/interactors in Clean Architecture
  • Design and implement use cases that orchestrate entities to achieve application goals
  • Define and use interfaces (ports) for data access and external services in Java

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

Think about a to-do app or a blog. What are the main actions a user can perform (e.g., add a task, mark as complete, create a post)? Why is it important to separate these actions from the details of how data is stored or displayed?

Lesson Overview

  • Duration: 90 minutes
  • Prerequisites: Understanding of entities and basic OOP
  • Materials Needed: Java development environment, pen and paper for sketching use cases

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. What are use cases/interactors?
  2. How use cases orchestrate entities and define application-specific business rules
  3. Defining interfaces (ports) for data access and external services
  4. Implementing use cases in Java
  5. Hands-on practice and exercises
  6. Common pitfalls and how to avoid them

Success Criteria:

By the end of this session, you should be able to design and implement use cases that are independent of frameworks and external details, and define interfaces for their dependencies.

3. Foundation Knowledge (15-20 minutes)

Core Concepts

What is a Use Case/Interactor?

A use case (sometimes called an interactor) is an application-specific business rule. It orchestrates the flow of data to and from entities to achieve a specific goal (e.g., adding a to-do item, creating a blog post).

Real-World Analogy: Think of a restaurant: the chef (entity) knows how to cook, but the waiter (use case) takes the order, coordinates with the chef, and ensures the meal is served to the right table. The waiter doesn’t care how the chef cooks, and the chef doesn’t care how the order was taken.

Why This Matters: Use cases keep your application logic organized and independent from frameworks, databases, and UI. This makes your application easier to test and adapt.

Key Responsibilities of Use Cases

  • Orchestrate entities to fulfill application goals
  • Implement application-specific business rules (not enterprise-wide)
  • Define interfaces (ports) for data access and external services
  • Use input/output models (DTOs) to communicate with other layers

Key Terminology

  • Use Case/Interactor: Application-specific business logic
  • Port: An interface that defines a contract for data access or external services
  • DTO (Data Transfer Object): Simple object for passing data between layers

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

Understanding Use Cases in Clean Architecture

Use cases are the second innermost layer. They depend on entities but are independent of frameworks, databases, and UI. They define what the application does, not how it is presented or stored.

Practical Applications

  • In a to-do app: AddTask, CompleteTask, ListTasks
  • In a blog: CreatePost, EditPost, ListPosts

Common Misconceptions

  1. Misconception: Use cases should contain database or UI code. Reality: Use cases should only depend on interfaces, not concrete implementations.
  2. Misconception: Use cases are just service classes. Reality: Use cases should focus on orchestrating entities and enforcing application-specific rules.

5. Live Coding Demonstration (20-25 minutes)

Example 1: Defining a Use Case and Port in Java

// Entity
public class Task {
    private String id;
    private String description;
    private boolean completed;
    // ...constructor and methods...
}

// Port (interface for data access)
public interface TaskRepository {
    void save(Task task);
    Task findById(String id);
}

// Use Case
public class AddTaskUseCase {
    private TaskRepository repository;
    public AddTaskUseCase(TaskRepository repository) {
        this.repository = repository;
    }
    public void execute(String id, String description) {
        Task task = new Task(id, description);
        repository.save(task);
    }
}

Explanation:

  • TaskRepository is an interface (port) defined by the use case layer.
  • AddTaskUseCase depends only on the interface, not the implementation.
  • The use case orchestrates the creation and saving of a task.

Example 2: Input/Output Models (DTOs)

// Input DTO
public class AddTaskRequest {
    public String id;
    public String description;
}

// Output DTO
public class AddTaskResponse {
    public boolean success;
    public String message;
}

// Use Case with DTOs
public class AddTaskUseCase {
    private TaskRepository repository;
    public AddTaskUseCase(TaskRepository repository) {
        this.repository = repository;
    }
    public AddTaskResponse execute(AddTaskRequest request) {
        if (request.description == null || request.description.isEmpty()) {
            AddTaskResponse response = new AddTaskResponse();
            response.success = false;
            response.message = "Description cannot be empty.";
            return response;
        }
        Task task = new Task(request.id, request.description);
        repository.save(task);
        AddTaskResponse response = new AddTaskResponse();
        response.success = true;
        response.message = "Task added successfully.";
        return response;
    }
}

Key Points:

  • Use cases use DTOs to communicate with other layers.
  • Business rules and validation are enforced in the use case.

Example 3: Defining Multiple Ports

// Port for notification service
public interface NotificationService {
    void sendTaskCreatedNotification(Task task);
}

// Use Case with multiple ports
public class AddTaskUseCase {
    private TaskRepository repository;
    private NotificationService notificationService;
    public AddTaskUseCase(TaskRepository repository, NotificationService notificationService) {
        this.repository = repository;
        this.notificationService = notificationService;
    }
    public void execute(String id, String description) {
        Task task = new Task(id, description);
        repository.save(task);
        notificationService.sendTaskCreatedNotification(task);
    }
}

Debugging Demonstration

Show a common error:

// Common mistake: Use case depends on a concrete repository
public class AddTaskUseCase {
    private InMemoryTaskRepository repository; // tightly coupled!
    public AddTaskUseCase(InMemoryTaskRepository repository) {
        this.repository = repository;
    }
    // ...
}

// Corrected version
public class AddTaskUseCase {
    private TaskRepository repository; // depends on interface
    public AddTaskUseCase(TaskRepository repository) {
        this.repository = repository;
    }
    // ...
}

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

Exercise 1: Basic Practice (10 minutes)

Objective: Define a use case and port for marking a to-do item as complete.

Instructions:

  1. Define a CompleteTaskUseCase class.
  2. Define a TaskRepository interface with findById and save methods.
  3. Implement the use case logic to mark a task as complete and save it.

Starter Code:

public interface TaskRepository {
    // ...
}

public class CompleteTaskUseCase {
    // ...
}

Solution:

public interface TaskRepository {
    Task findById(String id);
    void save(Task task);
}

public class CompleteTaskUseCase {
    private TaskRepository repository;
    public CompleteTaskUseCase(TaskRepository repository) {
        this.repository = repository;
    }
    public void execute(String id) {
        Task task = repository.findById(id);
        if (task != null) {
            task.markComplete();
            repository.save(task);
        }
    }
}

Exercise 2: Intermediate Challenge (15 minutes)

Objective: Add input/output DTOs and validation to a use case.

Requirements:

  • Create request and response DTOs for the CompleteTaskUseCase.
  • Add validation to ensure the task exists before marking as complete.
  • Return a response indicating success or failure.

Hints:

  • Use null checks and meaningful messages in the response DTO.

Exercise 3: Creative Application (15 minutes)

Objective: Design use cases and ports for a blog application (e.g., CreatePost, EditPost).

Challenge: Define the use case classes, repository interfaces, and input/output DTOs for creating and editing blog posts.

Extension Ideas:

  • Add a notification port for sending emails when a post is created.
  • Implement validation rules in the use cases.

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Mistakes

  1. Problem: Use cases depend on concrete classes Cause: Not using interfaces for abstraction Solution: Always depend on interfaces, not implementations

  2. Problem: Mixing data access or UI code in use cases Cause: Violating separation of concerns Solution: Keep use cases focused on business logic and orchestration

Debugging Tips

  • Check for imports from frameworks in use case classes
  • Use dependency injection to manage dependencies
  • Write unit tests for use cases with mock repositories

8. Best Practices and Professional Tips (5 minutes)

Industry Standards

  • Define ports (interfaces) in the use case layer
  • Implement ports in outer layers (interface adapters)
  • Use DTOs for input/output models

Next Steps in Your Learning Journey

  • Identify use cases in your own projects
  • Refactor service classes to follow Clean Architecture principles
  • Read about Command Query Responsibility Segregation (CQRS)

9. Summary and Review (5 minutes)

Key Takeaways

  1. Use cases orchestrate entities and enforce application-specific business rules
  2. Ports (interfaces) decouple use cases from data access and external services
  3. DTOs are used for input/output between layers

Quick Review Questions

  1. What is a use case/interactor in Clean Architecture?
  2. Why should use cases depend on interfaces (ports) instead of concrete classes?
  3. How do DTOs help in Clean Architecture?

For Instructors

Teaching Tips

  • Use real-world scenarios to illustrate use cases
  • Encourage students to identify and design use cases for familiar applications
  • Review use case code for separation of concerns and interface usage

Assessment Ideas

  • Ask students to design use cases and ports for a sample domain
  • Review code for correct use of DTOs and validation
  • Peer review of use case implementations

Adaptation Notes

  • For beginners, provide more scaffolding and examples
  • For advanced students, challenge them to design use cases for complex workflows
  • Adjust timing based on class experience and engagement