Clean Architecture
Clean Architecture is a software design philosophy that emphasizes separation of concerns to achieve high modularity, testability, and maintainability. It proposes structuring an application into concentric layers, with core business logic residing in the innermost layers and external concerns like databases, UI frameworks, and external APIs residing in the outermost layers. Dependencies point inwards, meaning inner layers have no knowledge of outer layers, promoting independence from technology changes and simplifying testing.
The primary goal of Clean Architecture is to create systems that are independent of frameworks, databases, UI, and any external agency. This independence allows for easier adaptation to changing requirements, improved testability (as business logic can be tested in isolation), and increased flexibility in choosing and swapping out technologies without impacting the core application. It achieves this through a strict dependency rule: source code dependencies can only point inwards.
Usage
Clean Architecture is commonly used in:
- Large, complex applications: Where maintainability and adaptability are crucial over the long term.
- Applications with evolving requirements: The decoupled nature allows for changes in one area without cascading effects.
- Systems requiring high testability: Inner layers can be tested easily without reliance on external dependencies.
- Microservices architecture: Each microservice can be built on Clean Architecture principles for better isolation and independence.
- Mobile Applications: When needing to support multiple platforms (iOS, Android) with shared core logic.
Examples
- Hexagonal Architecture (Ports and Adapters): Often considered a specific implementation of Clean Architecture, Hexagonal Architecture, used in many Java and .NET projects, explicitly defines ports (interfaces) that core logic interacts with, and adapters that connect those ports to external systems. Spring Framework often encourages this pattern through its dependency injection capabilities.
- Onion Architecture: Similar to Clean Architecture, Onion Architecture focuses on placing the core domain logic at the center and building layers of infrastructure around it. ASP.NET Core projects frequently adopt this structure, separating concerns into domain models, application services, and infrastructure layers.
- SwiftUI and Combine (Apple Ecosystem): Apple’s SwiftUI and Combine frameworks, while not explicitly enforcing Clean Architecture, lend themselves well to it. The MVVM (Model-View-ViewModel) pattern, often used with these frameworks, can be implemented within the Clean Architecture layers, with the ViewModel residing in the Use Cases layer and the Model representing Entities.
- Flask/Django with Core Logic Separation (Python Web Frameworks): Python web frameworks like Flask and Django can be structured to follow Clean Architecture principles. The core business logic is placed in separate modules, independent of the web framework’s specifics, allowing for easier testing and potential migration to other frameworks.
Specimens
15 implementationsClean Architecture aims to create systems independent of frameworks, databases, UI, and any external agency. It achieves this by organizing code into concentric layers: Entities (core business logic), Use Cases (application-specific logic), Interface Adapters (presenters, controllers, gateways), and Frameworks & Drivers (UI, databases). Dependencies point inwards – outer layers depend on inner layers, not the reverse. This makes the system testable, maintainable, and adaptable.
The Dart example below demonstrates a simplified Clean Architecture. entities/user.dart defines the core User entity. use_cases/login.dart contains the login use case, depending only on the entity. interface_adapters/login_controller.dart adapts the use case to a simple API. A console_app.dart represents the “Frameworks & Drivers” layer, handling user input and output in console format. Dart’s strong typing and support for both OOP and functional paradigms make it well-suited for this structure. Dependency Injection is implied through constructor parameters, adhering to the dependency inversion principle.
// entities/user.dart
class User {
final String username;
final String password;
User(this.username, this.password);
bool isValidPassword(String password) {
return this.password == password;
}
}
// use_cases/login.dart
abstract class LoginUseCase {
Future<User?> execute(String username, String password);
}
class LoginUseCaseImpl implements LoginUseCase {
final List<User> users;
LoginUseCaseImpl(this.users);
@override
Future<User?> execute(String username, String password) async {
await Future.delayed(Duration.zero); // Simulate async operation
for (final user in users) {
if (user.username == username && user.isValidPassword(password)) {
return user;
}
}
return null;
}
}
// interface_adapters/login_controller.dart
abstract class LoginController {
Future<String> login(String username, String password);
}
class LoginControllerImpl implements LoginController {
final LoginUseCase loginUseCase;
LoginControllerImpl(this.loginUseCase);
@override
Future<String> login(String username, String password) async {
final user = await loginUseCase.execute(username, password);
if (user != null) {
return 'Login successful for ${user.username}';
} else {
return 'Login failed';
}
}
}
// console_app.dart (Frameworks & Drivers)
import 'dart:io';
import 'package:clean_architecture_dart/entities/user.dart';
import 'package:clean_architecture_dart/use_cases/login.dart';
import 'package:clean_architecture_dart/interface_adapters/login_controller.dart';
void main() async {
final users = [User('user1', 'pass1'), User('user2', 'pass2')];
final loginUseCase = LoginUseCaseImpl(users);
final loginController = LoginControllerImpl(loginUseCase);
print('Enter username:');
final username = stdin.readLineSync() ?? '';
print('Enter password:');
final password = stdin.readLineSync() ?? '';
final result = await loginController.login(username, password);
print(result);
}
The Clean Architecture pattern separates an application into concentric layers: Entities, Use Cases, Interface Adapters, and Frameworks & Drivers. Inner layers are independent of outer ones, promoting testability, maintainability, and flexibility. This example demonstrates a simplified version focused on the core separation of Use Cases from its dependencies. The Core project defines Entities and Use Cases, independent of any framework. The Application project adapts the use cases to a specific implementation (e.g., using a repository for data access). This leverages Scala’s strong typing and immutability for clear dependency injection and testability. The use of traits allows for easy extension and swapping of implementations without affecting core business logic.
// Core project (independent of frameworks)
// entities/User.scala
package core.entities
case class User(id: Int, name: String)
// usecases/UserUseCase.scala
package core.usecases
import core.entities.User
trait UserUseCase {
def getUser(id: Int): Option[User]
}
// Application project (framework/driver specific)
// repositories/UserRepository.scala
package application.repositories
import core.entities.User
trait UserRepository {
def getById(id: Int): Option[User]
}
class InMemoryUserRepository extends UserRepository {
private val users: Map[Int, User] = Map(
1 -> User(1, "Alice"),
2 -> User(2, "Bob")
)
override def getById(id: Int): Option[User] = users.get(id)
}
// usecaseimpl/UserUseCaseImpl.scala
package application.usecaseimpl
import core.usecases.UserUseCase
import application.repositories.UserRepository
class UserUseCaseImpl(userRepository: UserRepository) extends UserUseCase {
override def getUser(id: Int): Option[User] = userRepository.getById(id)
}
// main.scala (entry point)
package application
import application.usecaseimpl.UserUseCaseImpl
import application.repositories.InMemoryUserRepository
object Main extends App {
val userRepository = new InMemoryUserRepository()
val userUseCase = new UserUseCaseImpl(userRepository)
val alice = userUseCase.getUser(1)
println(alice) // Output: Some(User(1,Alice))
val charlie = userUseCase.getUser(3)
println(charlie) // Output: None
}
The Clean Architecture pattern separates the application into concentric layers: Entities, Use Cases, Interface Adapters, and Frameworks & Drivers. The core business logic (Entities & Use Cases) is independent of external concerns like databases, UI, or frameworks. This promotes testability, maintainability, and adaptability.
The PHP example below demonstrates a simplified version. Entities represent the core data; Use Cases define application operations; Interface Adapters convert data between Use Cases and the framework (a basic CLI); and the Frameworks & Drivers layer handles external interaction. Dependency Injection is crucial, ensuring higher-level layers don’t depend on lower ones. This utilizes PHP’s flexible typing and focus on composition, fitting its typical application structure.
<?php
// Entities
class User
{
public function __construct(public string $id, public string $name) {}
}
// Use Cases
interface UserRepositoryInterface
{
public function getUser(string $id): ?User;
}
class GetUserUseCase
{
public function __construct(private UserRepositoryInterface $userRepository) {}
public function execute(string $id): ?User
{
return $this->userRepository->getUser($id);
}
}
// Interface Adapters
class UserConsolePresenter
{
public function presentUser(?User $user): void
{
if ($user) {
echo "User ID: " . $user->id . "\n";
echo "User Name: " . $user->name . "\n";
} else {
echo "User not found.\n";
}
}
}
// Frameworks & Drivers (CLI)
class UserConsole
{
public function __construct(private GetUserUseCase $getUserUseCase, private UserConsolePresenter $presenter) {}
public function handle(string $id): void
{
$user = $this->getUserUseCase->execute($id);
$this->presenter->presentUser($user);
}
}
// Concrete Implementation (for testing/dependency injection)
class InMemoryUserRepository implements UserRepositoryInterface
{
private array $users = [
'1' => new User('1', 'John Doe'),
'2' => new User('2', 'Jane Smith'),
];
public function getUser(string $id): ?User
{
return $this->users[$id] ?? null;
}
}
// Wiring
$userRepository = new InMemoryUserRepository();
$getUserUseCase = new GetUserUseCase($userRepository);
$presenter = new UserConsolePresenter();
$console = new UserConsole($getUserUseCase, $presenter);
// Run
if (isset($argv[1])) {
$console->handle($argv[1]);
} else {
echo "Please provide a user ID as an argument.\n";
}
The Clean Architecture pattern aims to create systems independent of frameworks, databases, UI, and external agencies. This is achieved by structuring the application into concentric layers: Entities (core business rules), Use Cases (application-specific logic), Interface Adapters (translate data between layers), and Frameworks & Drivers (outermost layer, infrastructure). The inner layers know nothing of the outer ones. This example demonstrates a basic implementation with Entities, a Use Case, and a rudimentary CLI interface as a Framework & Driver. The code uses Ruby’s inherent flexibility and a focus on small, testable classes which is idiomatic. Dependency Injection is used implicitly through method calls; a full DI container isn’t needed for this simple case.
# entities/user.rb
class User
attr_reader :id, :name, :email
def initialize(id, name, email)
@id = id
@name = name
@email = email
end
end
# use_cases/create_user.rb
class CreateUser
def initialize(user_repository)
@user_repository = user_repository
end
def call(name, email)
user = User.new(SecureRandom.uuid, name, email)
@user_repository.save(user)
user
end
end
# interface_adapters/user_repository_interface.rb
module UserRepositoryInterface
def save(user)
raise NotImplementedError
end
end
# frameworks_and_drivers/in_memory_user_repository.rb
require 'uuid'
class InMemoryUserRepository
include UserRepositoryInterface
def initialize
@users = []
end
def save(user)
@users << user
end
def find_by_id(id)
@users.find { |user| user.id == id }
end
end
# frameworks_and_drivers/cli.rb
class CLI
def initialize(create_user_use_case)
@create_user_use_case = create_user_use_case
end
def run
print "Enter user name: "
name = gets.chomp
print "Enter user email: "
email = gets.chomp
user = @create_user_use_case.call(name, email)
puts "Created user with ID: #{user.id}"
end
end
# main.rb
require_relative 'entities/user'
require_relative 'use_cases/create_user'
require_relative 'interface_adapters/user_repository_interface'
require_relative 'frameworks_and_drivers/in_memory_user_repository'
require_relative 'frameworks_and_drivers/cli'
user_repository = InMemoryUserRepository.new
create_user_use_case = CreateUser.new(user_repository)
cli = CLI.new(create_user_use_case)
cli.run
Clean Architecture aims to create systems independent of frameworks, databases, UI, and external agencies. It achieves this through layered organization: Entities (core business logic), Use Cases (application-specific business rules), Interface Adapters (presenters, controllers, gateways), and Frameworks & Drivers (UI, databases). Dependencies point inwards – outer layers depend on inner layers, but inner layers have no knowledge of outer layers. This promotes testability, maintainability, and flexibility.
The example demonstrates a simplified Clean Architecture for fetching user data. User is the Entity. GetUserUseCase is the Use Case, relying on a UserRepository protocol. UserRepositoryImpl adapts a hypothetical external source. UserController accepts input and presents the output, adhering to the presentation layer and depending on the Use Case. This structure uses protocols for dependency inversion, which is highly idiomatic in Swift, and allows for easy swapping of implementations (e.g. mock repositories for testing).
// MARK: - Entities
struct User {
let id: Int
let name: String
let email: String
}
// MARK: - Use Cases
protocol GetUserUseCase {
func getUser(id: Int) -> User?
}
struct DefaultGetUserUseCase: GetUserUseCase {
private let userRepository: UserRepository
init(userRepository: UserRepository) {
self.userRepository = userRepository
}
func getUser(id: Int) -> User? {
return userRepository.getUser(id: id)
}
}
// MARK: - Interface Adapters
protocol UserRepository {
func getUser(id: Int) -> User?
}
struct UserRepositoryImpl: UserRepository {
// Simulating a data source (e.g., API or database)
private let users: [User] = [
User(id: 1, name: "Alice", email: "alice@example.com"),
User(id: 2, name: "Bob", email: "bob@example.com")
]
func getUser(id: Int) -> User? {
return users.first { $0.id == id }
}
}
// MARK: - Frameworks & Drivers
struct UserController {
private let getUserUseCase: GetUserUseCase
init(getUserUseCase: GetUserUseCase) {
self.getUserUseCase = getUserUseCase
}
func getUser(id: Int) -> String {
if let user = getUserUseCase.getUser(id: id) {
return "User Name: \(user.name), Email: \(user.email)"
} else {
return "User not found."
}
}
}
// MARK: - Composition Root (Example Usage)
let userRepository = UserRepositoryImpl()
let getUserUseCase = DefaultGetUserUseCase(userRepository: userRepository)
let userController = UserController(getUserUseCase: getUserUseCase)
let user1Info = userController.getUser(id: 1)
print(user1Info)
let user3Info = userController.getUser(id: 3)
print(user3Info)
The Clean Architecture pattern aims to create systems independent of frameworks, databases, UI, and any external agency. It achieves this by dividing the application into concentric layers: Entities (business rules), Use Cases (application logic), Interface Adapters (presenters, controllers), and Frameworks & Drivers (UI, database). Dependencies point inwards – outer layers depend on inner layers, but not vice versa. This promotes testability, maintainability, and flexibility.
This Kotlin example focuses on the core layers – Entities, Use Cases, and a basic Interface Adapter. The User data class represents an Entity. GetUserUseCase demonstrates application logic, operating on Entities. UserController acts as an Interface Adapter, receiving input (e.g., from a web framework) and orchestrating the Use Case. Kotlin’s data classes, concise function definitions, and use of interfaces fit the pattern well, favouring immutability and clear separation of concerns. Dependency Injection (although not explicitly shown in this minimal implementation) would be used heavily in a real-world application.
// Entities
data class User(val id: Int, val name: String, val email: String)
// Use Cases
interface UserRepository {
fun getUser(id: Int): User?
}
class GetUserUseCase(private val userRepository: UserRepository) {
fun execute(userId: Int): User? {
return userRepository.getUser(userId)
}
}
// Interface Adapters
class UserController(private val getUserUseCase: GetUserUseCase) {
fun getUser(userId: Int): String {
val user = getUserUseCase.execute(userId)
return user?.let { "User: ${it.name}, Email: ${it.email}" } ?: "User not found"
}
}
// Frameworks & Drivers (Example - a simple in-memory repository)
class InMemoryUserRepository : UserRepository {
private val users = listOf(
User(1, "Alice", "alice@example.com"),
User(2, "Bob", "bob@example.com")
)
override fun getUser(id: Int): User? {
return users.firstOrNull { it.id == id }
}
}
fun main() {
val userRepository = InMemoryUserRepository()
val getUserUseCase = GetUserUseCase(userRepository)
val userController = UserController(getUserUseCase)
println(userController.getUser(1)) // Output: User: Alice, Email: alice@example.com
println(userController.getUser(3)) // Output: User not found
}
The Clean Architecture pattern separates the application into concentric layers: Entities, Use Cases, Interface Adapters, and Frameworks & Drivers. The core business logic resides in the inner layers (Entities & Use Cases) and is independent of external concerns like databases or UI. Outer layers implement interfaces defined by inner layers, controlling the flow of data to and from the core. This example demonstrates a simplified Clean Architecture with an entity, a use case, and a basic CLI interface adapter. Rust’s ownership and borrowing system aids in enforcing clear data flow boundaries, strong dependency inversion enabled through traits, and the compartmentalization central to this architecture.
// entities/user.rs
pub struct User {
pub id: u32,
pub name: String,
}
// use_cases/user_service.rs
pub trait UserRepository {
fn get_user(&self, id: u32) -> Option<User>;
}
pub struct UserService<'a> {
user_repository: &'a dyn UserRepository,
}
impl<'a> UserService<'a> {
pub fn new(user_repository: &'a dyn UserRepository) -> Self {
UserService { user_repository }
}
pub fn get_user_name(&self, id: u32) -> Option<String> {
self.user_repository.get_user(id).map(|user| user.name)
}
}
// interface_adapters/cli.rs
pub struct InMemoryUserRepository {
users: Vec<User>,
}
impl InMemoryUserRepository {
pub fn new(users: Vec<User>) -> Self {
InMemoryUserRepository { users }
}
}
impl UserRepository for InMemoryUserRepository {
fn get_user(&self, id: u32) -> Option<User> {
self.users.iter().find(|user| user.id == id).cloned()
}
}
pub fn main() {
let users = vec![
User { id: 1, name: "Alice".to_string() },
User { id: 2, name: "Bob".to_string() },
];
let user_repository = InMemoryUserRepository::new(users);
let user_service = UserService::new(&user_repository);
if let Some(name) = user_service.get_user_name(1) {
println!("User 1's name: {}", name);
} else {
println!("User 1 not found.");
}
}
Clean Architecture aims to create systems independent of frameworks, databases, UI, and external agencies. It achieves this through layering: Entities (core business rules), Use Cases (application-specific logic), Interface Adapters (converts data between layers), and Frameworks & Drivers (UI, databases, etc.). Dependencies point inward; inner layers know nothing of outer layers. This promotes testability, maintainability, and flexibility.
The Go example demonstrates a simplified Clean Architecture centered around a user profile use case. entities define the core User struct. usecases define the business logic for retrieving a user. interfaces define interfaces for data access (UserRepo) and presentation (UserPresenter). main (frameworks & drivers) implements a CLI driver and mocks the repository for this simple case. This design leverages Go’s interfaces for dependency inversion and separates concerns, aligning with its focus on explicit dependencies and simplicity.
// main.go - Frameworks & Drivers Layer
package main
import "fmt"
// Interfaces
type UserRepo interface {
GetUser(id int) (User, error)
}
type UserPresenter interface {
PresentUser(user User) string
}
// Entities
type User struct {
ID int
Name string
Email string
}
// Use Cases
type GetUserProfile struct {
userRepo UserRepo
userPresenter UserPresenter
}
func NewGetUserProfile(repo UserRepo, presenter UserPresenter) *GetUserProfile {
return &GetUserProfile{userRepo: repo, userPresenter: presenter}
}
func (u *GetUserProfile) GetUser(id int) (string, error) {
user, err := u.userRepo.GetUser(id)
if err != nil {
return "", err
}
return u.userPresenter.PresentUser(user), nil
}
// Implementations (for this example, in main for brevity)
type InMemoryUserRepo struct {
users map[int]User
}
func NewInMemoryUserRepo() *InMemoryUserRepo {
return &InMemoryUserRepo{users: map[int]User{
1: {ID: 1, Name: "John Doe", Email: "john.doe@example.com"},
}}
}
func (r *InMemoryUserRepo) GetUser(id int) (User, error) {
user, ok := r.users[id]
if !ok {
return User{}, fmt.Errorf("user not found")
}
return user, nil
}
type CLIUserPresenter struct{}
func (p *CLIUserPresenter) PresentUser(user User) string {
return fmt.Sprintf("User ID: %d, Name: %s, Email: %s", user.ID, user.Name, user.Email)
}
func main() {
repo := NewInMemoryUserRepo()
presenter := &CLIUserPresenter{}
useCase := NewGetUserProfile(repo, presenter)
profile, err := useCase.GetUser(1)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(profile)
}
The Clean Architecture pattern separates the application into concentric layers: Entities (core business logic), Use Cases (application-specific logic), Interface Adapters (translates data), and Frameworks & Drivers (UI, DB, etc.). Dependencies point inward – outer layers depend on inner layers, but inner layers have no knowledge of outer ones. This promotes testability, maintainability, and flexibility.
The example demonstrates a simple task list application. Entities are representations of a Task. Use Cases define operations like adding or listing tasks. Interface Adapters use a TaskPresenter to format task data for output. Frameworks and Drivers are minimal, using standard C library functions for input/output. Function pointers are used to achieve dependency inversion, allowing for easily swappable output mechanisms during testing or runtime. This structure and reliance on function pointers reflect C’s power for low-level control and callback-based designs, avoiding explicit inheritance found in some other languages.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Entities
typedef struct {
int id;
char description[100];
int completed;
} Task;
// Use Cases
typedef void (*TaskPresenter)(Task* tasks, int count);
typedef struct {
Task tasks[100];
int taskCount;
} TaskList;
void addTask(TaskList* taskList, const char* description) {
taskList->tasks[taskList->taskCount].id = taskList->taskCount + 1;
strncpy(taskList->tasks[taskList->taskCount].description, description, sizeof(taskList->tasks[taskList->taskCount].description) - 1);
taskList->tasks[taskList->taskCount].completed = 0;
taskList->taskCount++;
}
void listTasks(TaskList* taskList, TaskPresenter presenter) {
presenter(taskList->tasks, taskList->taskCount);
}
// Interface Adapters
void printTasksToConsole(Task* tasks, int count) {
for (int i = 0; i < count; i++) {
printf("%d: %s (Completed: %s)\n", tasks[i].id, tasks[i].description, tasks[i].completed ? "Yes" : "No");
}
}
// Frameworks & Drivers
int main() {
TaskList taskList;
taskList.taskCount = 0;
addTask(&taskList, "Buy groceries");
addTask(&taskList, "Wash the car");
addTask(&taskList, "Do laundry");
printf("Task List:\n");
listTasks(&taskList, printTasksToConsole); // Dependency Injection
return 0;
}
The Clean Architecture pattern separates the application into concentric layers: Entities (core business rules), Use Cases (application-specific logic), Interface Adapters (translates data between layers), and Frameworks & Drivers (UI, databases, etc.). Dependencies point inwards – outer layers depend on inner layers, but inner layers have no knowledge of outer ones. This promotes testability, maintainability, and independence from frameworks.
This C++ implementation focuses on the core principle of dependency inversion. Entities are simple data classes. Use Cases define application operations, depending only on Entity interfaces. Interface Adapters (e.g., Presenter) receive data from Use Cases and format it for the outer layer. The main function represents Frameworks & Drivers, orchestrating calls but remaining separate from business logic. Using interfaces and dependency injection allows swapping implementations without affecting core logic, aligning with C++ best practices of abstraction and modularity.
#include <iostream>
#include <string>
#include <vector>
// Entities
namespace Entities {
class User {
public:
std::string name;
int age;
};
}
// Use Cases
namespace UseCases {
class UserInteractor {
public:
virtual void getUser(int userId, Entities::User& user) = 0;
};
class GetUserUseCase : public UserInteractor {
public:
void getUser(int userId, Entities::User& user) override {
// In a real app, this would fetch from a repository.
// Here, we fake it for simplicity.
if (userId == 1) {
user.name = "Alice";
user.age = 30;
} else {
user.name = "Unknown";
user.age = 0;
}
}
};
}
// Interface Adapters
namespace InterfaceAdapters {
class Presenter {
public:
virtual void presentUser(const Entities::User& user) = 0;
};
class ConsolePresenter : public Presenter {
public:
void presentUser(const Entities::User& user) override {
std::cout << "User: Name = " << user.name << ", Age = " << user.age << std::endl;
}
};
}
int main() {
// Frameworks & Drivers (orchestration)
UseCases::GetUserUseCase userUseCase;
InterfaceAdapters::ConsolePresenter presenter;
Entities::User user;
userUseCase.getUser(1, user);
presenter.presentUser(user);
userUseCase.getUser(2, user);
presenter.presentUser(user);
return 0;
}
Clean Architecture aims to create systems independent of frameworks, databases, UI, and any external agency. It achieves this by structuring code into concentric layers: Entities (core business rules), Use Cases (application-specific logic), Interface Adapters (presenters, controllers, gateways), and Frameworks & Drivers (UI, databases). Dependencies point inward; inner layers know nothing of outer layers. This makes the system testable, maintainable, and adaptable to change.
The example demonstrates a simplified Clean Architecture with Entities, a Use Case, an Application (Interface Adapter) and a basic console framework. The User class represents an Entity. CreateUser is a Use Case. UserApplication is an Interface Adapter, translating Use Case results for the framework. The console app serves as the basic ‘Frameworks & Drivers’ layer, calling the application. Dependency Injection is used to decouple layers, fulfilling the inward dependency rule. This is aligned with common C# patterns like using interfaces for abstractions and separation of concerns.
// Entities
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// Use Cases
public interface ICreateUserUseCase
{
User Create(string name, string email);
}
public class CreateUserUseCase : ICreateUserUseCase
{
public User Create(string name, string email)
{
// In a real app, this would likely involve validation,
// checking for existing users, etc.
return new User { Id = Guid.NewGuid(), Name = name, Email = email };
}
}
// Application (Interface Adapter)
public interface IUserApplication
{
User CreateUser(string name, string email);
}
public class UserApplication : IUserApplication
{
private readonly ICreateUserUseCase _createUserUseCase;
public UserApplication(ICreateUserUseCase createUserUseCase)
{
_createUserUseCase = createUserUseCase;
}
public User CreateUser(string name, string email)
{
return _createUserUseCase.Create(name, email);
}
}
// Frameworks & Drivers (Console App)
public class Program
{
public static void Main(string[] args)
{
// Dependency Injection (Wiring up the layers)
var createUserUseCase = new CreateUserUseCase();
var userApplication = new UserApplication(createUserUseCase);
// Create a new user via the application layer
var newUser = userApplication.CreateUser("John Doe", "john.doe@example.com");
// Output the user details
Console.WriteLine($"User Created: Id = {newUser.Id}, Name = {newUser.Name}, Email = {newUser.Email}");
}
}
The Clean Architecture pattern advocates for separating concerns into distinct layers: Entities (core business rules), Use Cases (application-specific logic), Interface Adapters (presenters, controllers, gateways), and Frameworks & Drivers (database, UI). The goal is to make the core business logic independent of external concerns like databases or frameworks.
This TypeScript implementation demonstrates a simplified Clean Architecture. entities define core data structures. use-cases contain business logic—in this case, a simple user creation operation. interface-adapters handle request/response formatting, translating between use-case models and external formats. A controller receives input and invokes the use case, and a simple cli handles output. TypeScript’s strong typing and module system naturally support the separation of concerns. Dependency Injection isn’t explicitly shown for brevity, but is a common companion in a full implementation.
// entities/user.ts
export interface User {
id: string;
name: string;
email: string;
}
// use-cases/create-user.ts
import { User } from '../entities/user';
import { UserRepository } from '../interface-adapters/user-repository';
export interface CreateUserUseCase {
createUser(name: string, email: string): User;
}
export class CreateUser implements CreateUserUseCase {
constructor(private userRepository: UserRepository) {}
createUser(name: string, email: string): User {
const user = { id: crypto.randomUUID(), name, email };
this.userRepository.save(user);
return user;
}
}
// interface-adapters/user-repository.ts
import { User } from '../entities/user';
export interface UserRepository {
save(user: User): void;
getById(id: string): User | undefined;
}
// interface-adapters/user-presenter.ts
import { User } from '../entities/user';
export interface UserPresenter {
presentUser(user: User): string;
}
export class ConsoleUserPresenter implements UserPresenter {
presentUser(user: User): string {
return `User created with ID: ${user.id}, Name: ${user.name}, Email: ${user.email}`;
}
}
// frameworks & drivers/cli.ts
import { CreateUser, CreateUserUseCase } from '../use-cases/create-user';
import { InMemoryUserRepository } from '../interface-adapters/in-memory-user-repository';
import { ConsoleUserPresenter, UserPresenter } from '../interface-adapters/user-presenter';
class CLI {
constructor(
private createUserUseCase: CreateUserUseCase,
private userPresenter: UserPresenter
) {}
async run(): Promise<void> {
const name = prompt('Enter user name:');
const email = prompt('Enter user email:');
if (!name || !email) {
console.error('Name and email are required.');
return;
}
const newUser = this.createUserUseCase.createUser(name, email);
console.log(this.userPresenter.presentUser(newUser));
}
}
// framework and driver - for testing
class InMemoryUserRepository implements UserRepository {
private users: User[] = [];
save(user: User): void {
this.users.push(user);
}
getById(id: string): User | undefined {
return this.users.find(u => u.id === id);
}
}
// Main entry point (could be an Express route, etc.)
const userRepository = new InMemoryUserRepository(); //Injected dependency
const createUserUseCase = new CreateUser(userRepository);
const userPresenter = new ConsoleUserPresenter();
const cli = new CLI(createUserUseCase, userPresenter);
cli.run();
// Simple prompt function (necessary for a standalone example)
function prompt(message: string): string {
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise<string>((resolve) => {
readline.question(message, (answer) => {
readline.close();
resolve(answer);
});
});
}
The Clean Architecture pattern aims to create systems independent of frameworks, databases, UI, and any external agency. It achieves this through layers: Entities (core business logic), Use Cases (application logic), Interface Adapters (presenters, controllers), and Frameworks & Drivers (UI, databases). Dependencies point inwards – outer layers depend on inner layers, but never the reverse. This enhances testability, maintainability, and flexibility.
The JavaScript example demonstrates a simplified Clean Architecture. entities define core data structures. useCases encapsulate application-specific business rules, operating on entities. interfaceAdapters (specifically a controller) translate external requests into use case inputs and format use case outputs for presentation. This structure uses modules to enforce dependency direction. The use of ES module imports and exports keeps the code organized and adheres to modern JavaScript style. Testing is easier as use cases are isolated.
// entities/product.js
export class Product {
constructor(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
}
applyDiscount(percentage) {
this.price = this.price * (1 - percentage);
}
}
// useCases/apply-discount.js
import { Product } from '../entities/product.js';
export function applyDiscountToProduct(productId, discountPercentage) {
const product = new Product(productId, "Test Product", 100); //In reality, product retrieval would be a separate use case
product.applyDiscount(discountPercentage);
return product;
}
// interfaceAdapters/product-controller.js
import { applyDiscountToProduct } from '../useCases/apply-discount.js';
export async function handleApplyDiscount(req, res) {
try {
const productId = parseInt(req.query.productId);
const discountPercentage = parseFloat(req.query.discountPercentage);
if (isNaN(productId) || isNaN(discountPercentage)) {
return res.status(400).json({ error: 'Invalid input' });
}
const discountedProduct = applyDiscountToProduct(productId, discountPercentage);
res.json({
id: discountedProduct.id,
name: discountedProduct.name,
price: discountedProduct.price,
});
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Internal Server Error' });
}
}
// Example usage (Frameworks & Drivers - hypothetical Express route)
// app.get('/apply-discount', handleApplyDiscount);
The Clean Architecture pattern aims to create systems independent of frameworks, databases, UI, and any external agency. It achieves this through layered organization: Entities (business rules), Use Cases (application logic), Interface Adapters (translating data), and Frameworks & Drivers (external details). This example demonstrates a simplified structure with core business logic separated from external dependencies. It uses Python’s flexibility to define interfaces (abstract base classes) and dependency injection for loose coupling. The separation of concerns makes the core logic easily testable and adaptable to changes in frameworks or infrastructure without impact on the core.
from abc import ABC, abstractmethod
from dataclasses import dataclass
# Entities - Core Business Rules
@dataclass
class User:
id: int
name: str
email: str
# Use Cases - Application Logic
class UserRegistrationUseCase:
def __init__(self, user_repository):
self.user_repository = user_repository
def register_user(self, name: str, email: str) -> User:
user = User(id=self.user_repository.next_id(), name=name, email=email)
self.user_repository.save(user)
return user
# Interface Adapters - Data Translation
class UserRepositoryInterface(ABC):
@abstractmethod
def save(self, user: User):
pass
@abstractmethod
def get_by_id(self, user_id: int) -> User:
pass
@abstractmethod
def next_id(self) -> int:
pass
# Frameworks & Drivers - Implementation Details
class InMemoryUserRepository(UserRepositoryInterface):
def __init__(self):
self.users = {}
self.next_user_id = 1
def save(self, user: User):
self.users[user.id] = user
def get_by_id(self, user_id: int) -> User:
return self.users.get(user_id)
def next_id(self) -> int:
user_id = self.next_user_id
self.next_user_id += 1
return user_id
# Main Application - Wiring everything together
if __name__ == "__main__":
user_repository = InMemoryUserRepository()
registration_use_case = UserRegistrationUseCase(user_repository)
new_user = registration_use_case.register_user("Alice", "alice@example.com")
print(f"Registered user: {new_user}")
retrieved_user = user_repository.get_by_id(new_user.id)
print(f"Retrieved user: {retrieved_user}")
The Clean Architecture pattern aims to create systems independent of frameworks, databases, UI, and any external agency. It achieves this through layered architecture with an “inward rule” – dependencies point only inwards. The core business logic (Entities and Use Cases) resides in the inner layers, while outer layers (Interface Adapters & Frameworks and Drivers) contain implementation details. This promotes testability, maintainability, and flexibility.
The provided Java example demonstrates a simplified Clean Architecture for a user data management system. The entities package contains the User entity. usecases define the core business logic like RegisterUser. The interfaceadapters package features a UserPresenter which adapts data for presentation, and a UserGateway interface defining data access. Finally, frameworks includes a concrete UserRepository implementation using an in-memory data store and a simple CLI Main class acting as the driver. Dependency Injection is used to decouple layers. This structure aligns with Java’s packaging conventions and OOP principles, enhancing readability and scalability.
// entities/User.java
package entities;
public class User {
private final String username;
private final String email;
public User(String username, String email) {
this.username = username;
this.email = email;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", email='" + email + '\'' +
'}';
}
}
// usecases/RegisterUser.java
package usecases;
import entities.User;
import interfaces.UserGateway;
public class RegisterUser {
private final UserGateway userGateway;
public RegisterUser(UserGateway userGateway) {
this.userGateway = userGateway;
}
public boolean register(String username, String email) {
User newUser = new User(username, email);
return userGateway.saveUser(newUser);
}
}
// interfaceadapters/UserPresenter.java
package interfaceadapters;
import entities.User;
public class UserPresenter {
public static String presentUser(User user) {
return "Username: " + user.getUsername() + ", Email: " + user.getEmail();
}
}
// interfaces/UserGateway.java
package interfaces;
import entities.User;
public interface UserGateway {
boolean saveUser(User user);
User getUserByUsername(String username);
}
// frameworks/UserRepository.java
package frameworks;
import entities.User;
import interfaces.UserGateway;
import java.util.HashMap;
import java.util.Map;
public class UserRepository implements UserGateway {
private final Map<String, User> users = new HashMap<>();
@Override
public boolean saveUser(User user) {
if (users.containsKey(user.getUsername())) {
return false;
}
users.put(user.getUsername(), user);
return true;
}
@Override
public User getUserByUsername(String username) {
return users.get(username);
}
}
// frameworks/Main.java
package frameworks;
import usecases.RegisterUser;
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
RegisterUser registerUser = new RegisterUser(userRepository);
boolean registered = registerUser.register("john.doe", "john.doe@example.com");
if (registered) {
System.out.println("User registered successfully!");
} else {
System.out.println("User registration failed.");
}
}
}