Skip to main content

Overview

The infrastructure layer is where the “how” of your application lives. This layer implements the repository interfaces defined in the domain layer and handles all external dependencies like HTTP requests, databases, file systems, and third-party APIs. This is the only layer that should directly interact with:
  • HTTP clients (Angular’s HttpClient)
  • Database connections
  • External APIs
  • File systems
  • Framework-specific features

What Goes in the Infrastructure Layer

The infrastructure layer typically contains:
Concrete implementations of domain repository interfaces that handle data persistence and retrieval.Example: AuthService implements UsuarioRepository

Repository Implementation

The infrastructure layer implements repository interfaces defined in the domain layer. This is where HTTP requests, database queries, or any other data access mechanism is implemented.

AuthService: Implementing UsuarioRepository

Location: ~/workspace/source/src/app/usuario/infrastructure/services/auth-service.ts:1
auth-service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Usuario } from '../../domain/models/usuario';
import { UsuarioRepository } from '../../domain/repositories/usuario.repository';

@Injectable({
  providedIn: 'root',
})
export class AuthService implements UsuarioRepository {

  private readonly BASE_URL = 'http://localhost:8080/'
  constructor(private httpClient: HttpClient) { }

  loginUsuario(usuario: string, contrasena: string): Observable<Usuario> {
    return this.httpClient.get<Usuario>(this.BASE_URL + 'login' + '/' + usuario + '/' + contrasena);
  } 
  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' } }
    );
  }
}

Breaking Down AuthService

@Injectable
decorator
Makes the service available for dependency injection throughout the application
  • providedIn: 'root' means this is a singleton service available app-wide
  • Angular creates one instance and shares it everywhere
implements UsuarioRepository
interface implementation
Declares that this class fulfills the contract defined in the domain layer
  • TypeScript enforces that all methods from UsuarioRepository are implemented
  • Allows the service to be injected wherever UsuarioRepository is required
BASE_URL
string
Base URL for the authentication API
  • In production, this would come from environment configuration
  • Points to the backend server handling authentication
httpClient
HttpClient
Angular’s HTTP client for making requests
  • Injected via constructor dependency injection
  • Returns Observables for all HTTP operations
  • Handles request/response serialization automatically

HTTP Integration

Let’s examine how each method integrates with the backend API.

Login Implementation

auth-service.ts
loginUsuario(usuario: string, contrasena: string): Observable<Usuario> {
    return this.httpClient.get<Usuario>(this.BASE_URL + 'login' + '/' + usuario + '/' + contrasena);
}
HTTP Request Details:
  • Method: GET
  • URL: http://localhost:8080/login/{usuario}/{contrasena}
  • Response: JSON object matching the Usuario interface
  • Type Safety: get<Usuario>() tells TypeScript the response type
Using GET for login with credentials in the URL is not secure for production. Credentials should be sent in the request body with POST, and the URL should be served over HTTPS. URL parameters can be logged by servers, proxies, and appear in browser history.
Better approach for production:
loginUsuario(usuario: string, contrasena: string): Observable<Usuario> {
    const body = { usuario, contrasena };
    return this.httpClient.post<Usuario>(
        `${this.BASE_URL}login`,
        body,
        { headers: { 'Content-Type': 'application/json' } }
    );
}

Registration Implementation

auth-service.ts
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' } }
    );
}
HTTP Request Details:
  • Method: POST
  • URL: http://localhost:8080/registro
  • Body: { "usuario": "username", "contrasena": "password" }
  • Headers: Content-Type: application/json
  • Response: JSON object with the created user data
body
object
Request payload sent to the server
usuario
string
required
Username for the new account
contrasena
string
required
Password for the new account

Observable Pattern with HttpClient

Angular’s HttpClient returns Observables, which provide powerful asynchronous capabilities:
this.authService.loginUsuario('john', 'password123').subscribe({
  next: (usuario) => {
    console.log('Logged in:', usuario);
    // Navigate to dashboard
  },
  error: (err) => {
    console.error('Login failed:', err);
    // Show error message
  },
  complete: () => {
    console.log('Request complete');
  }
});

RxJS Operators for HTTP

Common RxJS operators used with HTTP requests:
OperatorPurposeExample
mapTransform response datamap(res => res.data)
catchErrorHandle errors gracefullycatchError(err => of(null))
tapSide effects (logging)tap(data => console.log(data))
retryRetry failed requestsretry(3)
timeoutCancel slow requeststimeout(5000)
switchMapChain dependent requestsswitchMap(user => getProfile(user.id))
Observables from HttpClient are cold and single-use. They don’t execute until you subscribe, and complete after one response.

Infrastructure Layer Structure

src/app/usuario/infrastructure/
├── services/
│   └── auth-service.ts        # Repository implementation
└── pages/
    ├── login/
    │   ├── login.component.ts
    │   ├── login.component.html
    │   └── login.component.css
    └── registro/
        ├── registro.component.ts
        ├── registro.component.html
        └── registro.component.css

UI Components (Pages)

UI components in the infrastructure layer consume handlers from the application layer:
login.component.ts
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { LoginUserHandler } from '../../application/usecases/queryHandlers/login-user.handler';
import { LoginUserQuery } from '../../application/queries/login-user.query';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent {
  username = '';
  password = '';
  error = '';

  constructor(
    private loginHandler: LoginUserHandler,
    private router: Router
  ) {}

  login() {
    const query = new LoginUserQuery(this.username, this.password);
    
    this.loginHandler.handle(query).subscribe({
      next: (usuario) => {
        localStorage.setItem('currentUser', JSON.stringify(usuario));
        this.router.navigate(['/products']);
      },
      error: (err) => {
        this.error = 'Invalid username or password';
      }
    });
  }
}
Component responsibilities:
  1. User Input: Collect username and password from the form
  2. Create Query: Build a LoginUserQuery with the input
  3. Call Handler: Use the injected LoginUserHandler
  4. Handle Response: Navigate on success, show error on failure
  5. State Management: Store user data (in localStorage, state management, etc.)

External Dependencies

The infrastructure layer is the only place that should import:
import { HttpClient, HttpHeaders } from '@angular/common/http';

Environment Configuration

In production applications, configuration should come from environment files:
auth-service.ts
import { environment } from '../../../environments/environment';

@Injectable({
  providedIn: 'root',
})
export class AuthService implements UsuarioRepository {
  private readonly BASE_URL = environment.apiUrl;
  
  // ... rest of implementation
}
environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:8080/'
};
environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.karma-ecommerce.com/'
};

Dependency Injection Configuration

To wire up the repository implementation, configure your Angular module:
usuario.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { UsuarioRepository } from '../domain/repositories/usuario.repository';
import { AuthService } from './services/auth-service';
import { RegisterUserHandler } from '../application/usecases/commandHandlers/register-user.handler';
import { LoginUserHandler } from '../application/usecases/queryHandlers/login-user.handler';

@NgModule({
  declarations: [
    // Components go here
  ],
  imports: [
    CommonModule,
    HttpClientModule  // Required for HttpClient
  ],
  providers: [
    // Provide the concrete implementation for the abstract repository
    { provide: UsuarioRepository, useClass: AuthService },
    // Register handlers
    RegisterUserHandler,
    LoginUserHandler
  ]
})
export class UsuarioModule {}
The key line is { provide: UsuarioRepository, useClass: AuthService }. This tells Angular’s DI system: “Whenever someone requests UsuarioRepository, provide an instance of AuthService.”

Testing Infrastructure Layer

Testing HTTP services with mocked HttpClient:
auth-service.spec.ts
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { AuthService } from './auth-service';
import { Usuario } from '../../domain/models/usuario';

describe('AuthService', () => {
  let service: AuthService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [AuthService]
    });
    
    service = TestBed.inject(AuthService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify(); // Ensure no outstanding requests
  });

  it('should login user successfully', () => {
    const mockUser: Usuario = {
      id: 1,
      usuario: 'john',
      contrasena: 'hashed_password'
    };

    service.loginUsuario('john', 'password123').subscribe(user => {
      expect(user).toEqual(mockUser);
    });

    const req = httpMock.expectOne('http://localhost:8080/login/john/password123');
    expect(req.request.method).toBe('GET');
    req.flush(mockUser); // Simulate server response
  });
});

Build docs developers (and LLMs) love