Skip to main content

Overview

The application layer sits between the domain and infrastructure layers. It orchestrates the application’s business logic by coordinating domain entities and repository interfaces without knowing the implementation details. This layer implements the use cases of your application - the specific tasks users want to accomplish.

Purpose of the Application Layer

The application layer:
  1. Orchestrates domain logic and repository calls
  2. Implements use cases as handlers
  3. Separates commands (writes) from queries (reads) using CQRS
  4. Coordinates transactions and business workflows
  5. Remains framework-aware but infrastructure-agnostic
The application layer can depend on Angular’s dependency injection (@Injectable) but should not directly depend on HTTP, routing, or UI components.

CQRS Pattern: Commands vs Queries

CQRS (Command Query Responsibility Segregation) is a pattern that separates read operations from write operations.
Commands represent actions that change state in the system.
  • Create, Update, Delete operations
  • Return the created/modified entity or void
  • May trigger side effects (emails, events, etc.)
  • Example: RegisterUserCommand, UpdateProfileCommand

Benefits of CQRS

  • Clear Intent: Immediately understand if an operation changes data
  • Independent Scaling: Optimize reads and writes separately
  • Security: Easier to apply different permissions to commands vs queries
  • Testing: Simpler to test side effects in commands only

Commands

Commands are simple DTOs (Data Transfer Objects) that carry the data needed to perform an action.

RegisterUserCommand

Location: ~/workspace/source/src/app/usuario/application/commands/register-user.command.ts:1
register-user.command.ts
export class RegisterUserCommand {
    constructor( readonly usuario: string, readonly contrasena: string){}
}
usuario
string
required
Username for the new account
contrasena
string
required
Password for the new account
Commands are immutable (using readonly) to prevent accidental modification during processing.

Queries

Queries are also simple DTOs that carry parameters needed to retrieve data.

LoginUserQuery

Location: ~/workspace/source/src/app/usuario/application/queries/login-user.query.ts:1
login-user.query.ts
export class LoginUserQuery {
    constructor(readonly usuario: string, readonly contrasena: string) {}
}
usuario
string
required
Username to authenticate
contrasena
string
required
Password for authentication
While LoginUserQuery is technically a read operation, it could be argued that authentication creates sessions (state change). In a more complex system, you might split this into AuthenticateUserQuery (check credentials) and CreateSessionCommand (create session).

Handlers: Use Case Implementation

Handlers contain the actual business logic for processing commands and queries. They depend on repository interfaces (from the domain layer) and coordinate the workflow.

Command Handler Example

Location: ~/workspace/source/src/app/usuario/application/usecases/commandHandlers/register-user.handler.ts:1
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 aspects:
  1. @Injectable(): Makes this class available for Angular’s dependency injection
  2. Constructor Injection: Receives UsuarioRepository (the interface, not the implementation)
  3. handle() method: Processes the command and returns an Observable
  4. Delegates to repository: Doesn’t know how the user is registered, only that it should be

Query Handler Example

Location: ~/workspace/source/src/app/usuario/application/usecases/queryHandlers/login-user.handler.ts:1
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);
    }
}
Similar structure to command handlers:
  • Receives a query object
  • Uses the repository interface
  • Returns an Observable with the result
  • Remains agnostic to implementation details

Observable Pattern (RxJS)

Both handlers return Observable<Usuario>, which is part of the reactive programming paradigm using RxJS.
Observables are used because:
  • Asynchronous: Handle HTTP requests that take time
  • Composable: Can be chained with operators (map, filter, catchError)
  • Cancellable: Can cancel pending requests
  • Angular Integration: Works seamlessly with Angular’s async pipe

Application Layer Structure

src/app/usuario/application/
├── commands/
│   └── register-user.command.ts     # Command DTOs
├── queries/
│   └── login-user.query.ts          # Query DTOs
└── usecases/
    ├── commandHandlers/
    │   └── register-user.handler.ts # Command handlers
    └── queryHandlers/
        └── login-user.handler.ts    # Query handlers

Handler Pattern Benefits

Each handler does one thing. RegisterUserHandler only handles user registration, nothing else.

Adding Business Logic to Handlers

In more complex scenarios, handlers would include validation, business rules, and coordination:
export class RegisterUserHandler {
    constructor(
        private usuarioRepository: UsuarioRepository,
        private passwordValidator: PasswordValidator,
        private emailService: EmailService
    ) {}

    handle(command: RegisterUserCommand): Observable<Usuario> {
        // Validate password strength
        if (!this.passwordValidator.isStrong(command.contrasena)) {
            return throwError(() => new Error('Password too weak'));
        }

        // Register user
        return this.usuarioRepository.registrarUsuario(
            command.usuario,
            command.contrasena
        ).pipe(
            // Send welcome email after successful registration
            tap(user => this.emailService.sendWelcomeEmail(user.usuario)),
            // Log the registration
            tap(user => console.log('User registered:', user.id))
        );
    }
}

Dependency Injection Setup

Handlers must be registered in an Angular module:
import { NgModule } from '@angular/core';
import { RegisterUserHandler } from './usecases/commandHandlers/register-user.handler';
import { LoginUserHandler } from './usecases/queryHandlers/login-user.handler';
import { UsuarioRepository } from '../domain/repositories/usuario.repository';
import { AuthService } from '../infrastructure/services/auth-service';

@NgModule({
  providers: [
    RegisterUserHandler,
    LoginUserHandler,
    { provide: UsuarioRepository, useClass: AuthService }
  ]
})
export class UsuarioModule {}
The key line is { provide: UsuarioRepository, useClass: AuthService }, which tells Angular: “When someone asks for UsuarioRepository, give them an instance of AuthService.”

Build docs developers (and LLMs) love