Interface Adapters: Gateways and Repositories
Learning Objectives: By the end of this lesson, you will be able to:
- Explain the role of gateways and repositories in Clean Architecture
- Implement repository interfaces and their concrete adapters in Java
- Understand how to keep data access logic separate from business logic
1. Welcome and Introduction (5 minutes)
Icebreaker Activity
Have you ever changed a database or external service in a project? What challenges did you face? How would it help if your core logic didn’t care about the details of data storage?
Lesson Overview
- Duration: 90 minutes
- Prerequisites: Understanding of use cases, entities, and interface adapters (controllers/presenters)
- Materials Needed: Java development environment, pen and paper for sketching repository interfaces
2. Learning Objectives and Session Outline (5 minutes)
What We’ll Cover Today:
- What are gateways and repositories?
- The Dependency Inversion Principle (DIP) in action
- Implementing repository interfaces and adapters in Java
- Hands-on practice and exercises
- Common pitfalls and how to avoid them
Success Criteria:
By the end of this session, you should be able to design and implement repository interfaces and adapters that keep your business logic independent of data storage details.
3. Foundation Knowledge (15-20 minutes)
Core Concepts
What are Gateways and Repositories?
- Gateways/Repositories are interface adapters that implement the interfaces (ports) defined by the use case layer for data access or external services.
- They contain the actual data access logic (e.g., SQL queries, HTTP calls) and convert data between the database/external service format and the entity format.
Real-World Analogy: Think of a universal power adapter: it connects your device (core logic) to any power outlet (database, API) without your device needing to know the details.
Why This Matters: This separation allows you to swap databases or external services with minimal changes to your core logic.
The Dependency Inversion Principle (DIP)
- Inner layers (use cases) define interfaces for what they need.
- Outer layers (adapters) implement these interfaces.
- This inverts the traditional dependency direction, making the core logic independent of details.
Key Terminology
- Repository: An interface for data access, defined by the use case layer
- Gateway: A broader term for any adapter to an external system (database, API, file, etc.)
- Adapter: A concrete implementation of a repository or gateway
4. Detailed Explanation and Examples (25-30 minutes)
Understanding Gateways and Repositories in Clean Architecture
Repositories and gateways are the bridge between your core logic and the outside world. They allow you to change data sources or external services without affecting your business rules.
Practical Applications
- Switching from an in-memory database to a SQL or NoSQL database
- Integrating with a new external API for notifications or payments
Common Misconceptions
- Misconception: Use cases should know about database details. Reality: Use cases should only depend on repository interfaces, not implementations.
- Misconception: Repositories should contain business logic. Reality: Repositories should only handle data access and conversion.
5. Live Coding Demonstration (20-25 minutes)
Example 1: Repository Interface and In-Memory Adapter in Java
// Repository interface (defined in use case layer)
public interface TaskRepository {
void save(Task task);
Task findById(String id);
}
// In-memory adapter (implements the interface)
import java.util.HashMap;
import java.util.Map;
public class InMemoryTaskRepository implements TaskRepository {
private Map<String, Task> tasks = new HashMap<>();
public void save(Task task) {
tasks.put(task.getId(), task);
}
public Task findById(String id) {
return tasks.get(id);
}
}
Explanation:
- The use case depends on
TaskRepository, not the concrete implementation. - The adapter implements the interface and handles data storage details.
Example 2: SQL Adapter (Conceptual)
// SQL adapter (conceptual, not full implementation)
public class SqlTaskRepository implements TaskRepository {
public void save(Task task) {
// SQL code to insert or update task
}
public Task findById(String id) {
// SQL code to fetch task by ID
return null; // Replace with actual retrieval logic
}
}
Key Points:
- You can swap
InMemoryTaskRepositoryforSqlTaskRepositorywithout changing the use case. - The use case layer is unaware of how data is stored.
Example 3: Gateway for External Service
// Notification gateway interface
public interface NotificationGateway {
void sendTaskCreated(Task task);
}
// Email adapter
public class EmailNotificationGateway implements NotificationGateway {
public void sendTaskCreated(Task task) {
// Code to send email notification
}
}
Debugging Demonstration
Show a common error:
// Common mistake: Use case depends on concrete adapter
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: Implement an in-memory repository for blog posts.
Instructions:
- Define a
BlogPostRepositoryinterface withsaveandfindByIdmethods. - Implement an
InMemoryBlogPostRepositoryclass.
Starter Code:
public interface BlogPostRepository {
// ...
}
public class InMemoryBlogPostRepository implements BlogPostRepository {
// ...
}
Solution:
public interface BlogPostRepository {
void save(BlogPost post);
BlogPost findById(String id);
}
import java.util.HashMap;
import java.util.Map;
public class InMemoryBlogPostRepository implements BlogPostRepository {
private Map<String, BlogPost> posts = new HashMap<>();
public void save(BlogPost post) {
posts.put(post.getId(), post);
}
public BlogPost findById(String id) {
return posts.get(id);
}
}
Exercise 2: Intermediate Challenge (15 minutes)
Objective: Implement a gateway for an external notification service.
Requirements:
- Define a
NotificationGatewayinterface. - Implement an adapter that prints notifications to the console.
- Use the gateway in a use case.
Hints:
- Use dependency injection to provide the gateway to the use case.
Exercise 3: Creative Application (15 minutes)
Objective: Design repository interfaces and adapters for a library system (Book, Member, Loan).
Challenge: Define repository interfaces and in-memory adapters for each entity. Consider how you would swap to a database implementation later.
Extension Ideas:
- Add gateways for external services (e.g., overdue notifications)
- Implement adapters for file-based or remote storage
7. Common Pitfalls and Troubleshooting (10 minutes)
Frequent Mistakes
-
Problem: Use cases depend on concrete adapters Cause: Not using interfaces for abstraction Solution: Always depend on interfaces, not implementations
-
Problem: Business logic in repositories Cause: Mixing concerns Solution: Keep repositories focused on data access and conversion
Debugging Tips
- Check for imports from frameworks in use case classes
- Use dependency injection to manage adapters
- Write unit tests for use cases with mock repositories
8. Best Practices and Professional Tips (5 minutes)
Industry Standards
- Define repository interfaces in the use case layer
- Implement adapters in the interface adapters layer
- Use dependency injection frameworks for wiring
Next Steps in Your Learning Journey
- Refactor existing code to use repository interfaces and adapters
- Explore Spring Data or similar frameworks for repository patterns
- Read about the Adapter and Repository patterns in depth
9. Summary and Review (5 minutes)
Key Takeaways
- Gateways and repositories decouple business logic from data access and external services
- The Dependency Inversion Principle keeps core logic independent of details
- Adapters can be swapped without changing use cases
Quick Review Questions
- What is the role of a repository in Clean Architecture?
- Why should use cases depend on interfaces, not concrete adapters?
- How does the Dependency Inversion Principle help maintainability?
For Instructors
Teaching Tips
- Use real-world data access scenarios to illustrate repositories and gateways
- Encourage students to refactor code to use interfaces and adapters
- Review repository and gateway code for separation of concerns
Assessment Ideas
- Ask students to implement repository interfaces and adapters for a sample domain
- Review code for correct use of dependency injection
- Peer review of adapter implementations
Adaptation Notes
- For beginners, provide more scaffolding and examples
- For advanced students, challenge them to design adapters for multiple data sources
- Adjust timing based on class experience and engagement