Clean Architecture & Domain-Driven Design
This project implements Clean Architecture with Domain-Driven Design (DDD) principles and the CQRS (Command Query Responsibility Segregation) pattern.
What is Clean Architecture?
Clean Architecture, introduced by Robert C. Martin (Uncle Bob), is a software design philosophy that emphasizes:
Independence Business logic is independent of frameworks, UI, and databases
Testability Business rules can be tested without external dependencies
Flexibility UI and infrastructure can change without affecting business logic
Maintainability Clear separation of concerns makes code easier to maintain
The Dependency Rule
The fundamental rule is that dependencies point inward :
Infrastructure → Application → Domain
(outer) (middle) (core)
Domain knows nothing about Application or Infrastructure
Application knows about Domain, but not Infrastructure
Infrastructure knows about both Application and Domain
No code in an inner layer can reference anything in an outer layer. This includes functions, classes, variables, or any other named software entity.
Domain-Driven Design (DDD)
DDD focuses on modeling software based on the business domain. Key concepts:
Entities and Value Objects
In our usuario module, Usuario is an entity:
src/app/usuario/domain/models/usuario.ts
export interface Usuario {
id : number ,
usuario : string ,
contrasena : string
}
Characteristics :
Has a unique identifier (id)
Represents a business concept (user)
Lives in the Domain layer
No framework dependencies
Repository Pattern
Repositories provide an abstraction for data access:
src/app/usuario/domain/repositories/usuario.repository.ts
import { Observable } from "rxjs" ;
import { Usuario } from "../models/usuario" ;
export abstract class UsuarioRepository {
abstract loginUsuario ( usuario : string , contrasena : string ) : Observable < Usuario >;
abstract registrarUsuario ( usuario : string , contrasena : string ) : Observable < Usuario >;
}
Key Points :
Abstract class defines the interface (contract)
Located in Domain layer
Implementation is in Infrastructure layer
This is Dependency Inversion in action
Why use abstract classes instead of interfaces?
In TypeScript/Angular, abstract classes can be used as injection tokens for dependency injection, while interfaces cannot (they disappear after compilation). This allows: constructor ( private usuarioRepository : UsuarioRepository ) {}
Angular can inject the concrete implementation at runtime.
Domain Services vs Application Services
Domain Services : Contain business logic that doesn’t belong to a single entity
Application Services : Orchestrate use cases using domain objects and repositories
In this project, handlers are application services.
CQRS Pattern
Command Query Responsibility Segregation separates read and write operations:
Commands Write Operations - Modify state
Queries Read Operations - Retrieve data
Commands: Write Operations
Commands represent intent to change state:
src/app/usuario/application/commands/register-user.command.ts
export class RegisterUserCommand {
constructor (
readonly usuario : string ,
readonly contrasena : string
) {}
}
Characteristics :
Immutable (readonly properties)
Descriptive name (verb-based: Register, Create, Update)
Contains all data needed for the operation
No business logic
Command Handlers
Handlers execute commands:
src/app/usuario/application/usecases/commandHandlers/register-user.handler.ts
import { Injectable } from "@angular/core" ;
import { UsuarioRepository } from "../../../domain/repositories/usuario.repository" ;
import { RegisterUserCommand } from "../../commands/register-user.command" ;
import { Observable } from "rxjs" ;
import { Usuario } from "../../../domain/models/usuario" ;
@ Injectable ()
export class RegisterUserHandler {
constructor ( private usuarioRepository : UsuarioRepository ) {}
handle ( command : RegisterUserCommand ) : Observable < Usuario > {
return this . usuarioRepository . registrarUsuario (
command . usuario ,
command . contrasena
);
}
}
Key Points :
Depends on UsuarioRepository interface (from Domain)
Injectable for dependency injection
Single responsibility: handle one command
Returns Observable for async operations
Queries: Read Operations
Queries represent requests for data:
src/app/usuario/application/queries/login-user.query.ts
export class LoginUserQuery {
constructor (
readonly usuario : string ,
readonly contrasena : string
) {}
}
In this case, “login” is a query because it reads user data to verify credentials. It doesn’t modify the user entity.
Query Handlers
Handlers execute queries:
src/app/usuario/application/usecases/queryHandlers/login-user.handler.ts
import { Observable } from "rxjs" ;
import { UsuarioRepository } from "../../../domain/repositories/usuario.repository" ;
import { LoginUserQuery } from "../../queries/login-user.query" ;
import { Usuario } from "../../../domain/models/usuario" ;
import { Injectable } from "@angular/core" ;
@ Injectable ()
export class LoginUserHandler {
constructor ( private usuarioRepository : UsuarioRepository ) {}
handle ( query : LoginUserQuery ) : Observable < Usuario > {
return this . usuarioRepository . loginUsuario (
query . usuario ,
query . contrasena
);
}
}
Pattern :
Same structure as command handlers
Separated by intent (query vs command)
Allows different optimizations for reads vs writes
Dependency Inversion Principle
The Dependency Inversion Principle (DIP) states:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
How It Works in This Project
Define Interface in Domain
The abstract repository lives in the Domain layer: // domain/repositories/usuario.repository.ts
export abstract class UsuarioRepository {
abstract registrarUsuario (...) : Observable < Usuario >;
}
Implement in Infrastructure
The concrete implementation lives in Infrastructure: // infrastructure/services/auth-service.ts
export class AuthService implements UsuarioRepository {
constructor ( private httpClient : HttpClient ) {}
registrarUsuario ( usuario : string , contrasena : string ) : Observable < Usuario > {
return this . httpClient . post < Usuario >(
` ${ this . BASE_URL } registro` ,
{ usuario , contrasena }
);
}
}
Use in Application
Handlers depend on the abstraction, not the implementation: // application/usecases/commandHandlers/register-user.handler.ts
export class RegisterUserHandler {
constructor ( private usuarioRepository : UsuarioRepository ) {}
handle ( command : RegisterUserCommand ) : Observable < Usuario > {
return this . usuarioRepository . registrarUsuario ( ... );
}
}
Benefits
Easy Testing // Test with a mock repository
const mockRepo = {
registrarUsuario : () => of ( mockUser )
};
const handler = new RegisterUserHandler ( mockRepo );
Swappable Implementations // Switch from HTTP to LocalStorage
// without changing handlers
class LocalStorageUserRepository
implements UsuarioRepository { ... }
Real-World Flow Example
Let’s trace a complete user registration flow:
User Action
User fills registration form and clicks submit // infrastructure/ui/pages/register-page/register-page.ts
registrar ( usuario : string , contrasena : string ) {
this . authService . registrarUsuario ( usuario , contrasena )
. pipe (
tap ( usuario => {
console . log ( 'usuario' , usuario );
usuario ? alert ( 'usuario registrado correctamente' ) : alert ( usuario );
}),
catchError ( err => {
alert ( `Error al registrar: ${ err . error } ` );
return throwError (() => err );
})
). subscribe ();
}
Service Layer
AuthService (Infrastructure) implements the repository interface: // infrastructure/services/auth-service.ts
@ Injectable ({ providedIn: 'root' })
export class AuthService implements UsuarioRepository {
private readonly BASE_URL = 'http://localhost:8080/'
registrarUsuario ( usuario : string , contrasena : string ) : Observable < Usuario > {
const body = { usuario , contrasena };
return this . httpClient . post < Usuario >(
` ${ this . BASE_URL } registro` ,
body ,
{ headers: { 'Content-Type' : 'application/json' } }
);
}
}
Handler (Alternative Flow)
If using handlers, the component would call a handler instead: // Create command
const command = new RegisterUserCommand ( usuario , contrasena );
// Execute via handler
this . registerUserHandler . handle ( command ). subscribe ( ... );
Domain Contract
All layers respect the domain contract: // domain/repositories/usuario.repository.ts
export abstract class UsuarioRepository {
abstract registrarUsuario (
usuario : string ,
contrasena : string
) : Observable < Usuario >;
}
Best Practices
Naming Conventions
Commands : {Verb}{Entity}Command (e.g., RegisterUserCommand, UpdateProfileCommand)
Queries : {Verb}{Entity}Query (e.g., GetUserQuery, ListProductsQuery)
Handlers : {Verb}{Entity}Handler (e.g., RegisterUserHandler, LoginUserHandler)
Repositories : {Entity}Repository (e.g., UsuarioRepository, ProductRepository)
When to Use Commands vs Queries
Create new entities
Update existing entities
Delete entities
Any operation that changes system state
May have side effects (send email, update cache, etc.)
Fetch single entity
List multiple entities
Search/filter entities
Login (reads user data for verification)
No side effects on domain state
Layer Responsibilities
Layer Responsibilities What NOT to Include Domain Entities, value objects, repository interfaces, business rules Framework code, HTTP calls, UI logic Application Commands, queries, handlers, use case orchestration HTTP implementation, Angular components Infrastructure UI components, HTTP services, routing, external APIs Business rules, domain logic
Benefits in Practice
Testability Example
// Easy to test handlers with mocks
describe ( 'RegisterUserHandler' , () => {
it ( 'should register user' , ( done ) => {
const mockRepo = {
registrarUsuario: jasmine . createSpy (). and . returnValue (
of ({ id: 1 , usuario: 'test' , contrasena: 'pass' })
)
};
const handler = new RegisterUserHandler ( mockRepo as any );
const command = new RegisterUserCommand ( 'test' , 'pass' );
handler . handle ( command ). subscribe ( result => {
expect ( result . usuario ). toBe ( 'test' );
expect ( mockRepo . registrarUsuario ). toHaveBeenCalled ();
done ();
});
});
});
Maintainability Example
Need to switch from REST API to GraphQL?
// Just create a new service implementing the same interface
@ Injectable ()
export class GraphQLAuthService implements UsuarioRepository {
constructor ( private apollo : Apollo ) {}
registrarUsuario ( usuario : string , contrasena : string ) : Observable < Usuario > {
return this . apollo . mutate ({
mutation: REGISTER_USER_MUTATION ,
variables: { usuario , contrasena }
}). pipe ( map ( result => result . data . registerUser ));
}
}
// Update provider - no other code changes needed!
Next Steps
Folder Structure Explore the complete directory structure and conventions
Architecture Overview Review the high-level architecture diagram