Retour aux articles

Appliquer la Clean Architecture à une application Spring Boot et Angular

Appliquer la Clean Architecture à une application Spring Boot et Angular | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular

Appliquer la Clean Architecture à une application Spring Boot et Angular

Dans le monde du développement logiciel, la construction d'applications robustes, maintenables et évolutives est une quête permanente. Au cœur de cette démarche se trouve l'architecture logicielle, un pilier fondamental qui détermine la qualité et la durabilité d'un système. La Clean Architecture, popularisée par Robert C. Martin (Uncle Bob), offre une approche structurée pour atteindre ces objectifs en isolant les logiques métier des détails techniques et des frameworks.

Cet article explore comment appliquer les principes de la Clean Architecture à une application moderne développée avec Spring Boot pour le backend et Angular pour le frontend. Pour un développeur Full Stack expert en Java Spring Boot et Angular comme Laty Gueye Samba, basé à Dakar, cette approche est essentielle pour gérer la complexité des applications métier et des systèmes ERP, garantissant ainsi une conception système solide et performante.

L'objectif principal de la Clean Architecture est de créer des systèmes indépendants du framework, de la base de données, de l'UI et de tout service externe, tout en maximisant la testabilité. Elle repose sur la règle de dépendance : les dépendances doivent toujours pointer vers l'intérieur, des couches externes vers les couches internes, assurant que la logique métier reste le cœur non affecté par les changements périphériques.

Comprendre les Principes Fondamentaux de la Clean Architecture

La Clean Architecture est souvent représentée par une série de cercles concentriques, chacun représentant une couche du système avec des responsabilités spécifiques. La règle cardinale est la "Dependency Rule" : le code des couches intérieures ne doit avoir aucune connaissance du code des couches extérieures. Les dépendances ne peuvent aller que de l'extérieur vers l'intérieur.

Les couches typiques sont les suivantes, de l'intérieur vers l'extérieur :

  • Entités (Entities) : Contiennent les règles métier les plus générales et les plus stables. Ce sont des objets métier avec des méthodes.
  • Cas d'Utilisation (Use Cases) / Interactors : Contiennent les règles métier spécifiques à l'application. Ils orchestrent le flux de données vers et depuis les entités et délèguent des tâches aux adaptateurs.
  • Adaptateurs d'Interface (Interface Adapters) : Convertissent les données du format le plus pratique pour les cas d'utilisation et les entités au format le plus pratique pour les agents externes (bases de données, web, UI). Inclut les contrôleurs, les gateways (implémentations de dépôts), et les présentateurs.
  • Frameworks et Pilotes (Frameworks and Drivers) : La couche la plus externe, composée de tous les outils et frameworks. Elle inclut la base de données, le framework web, l'UI, etc.

Cette séparation des préoccupations rend l'application hautement testable et facile à modifier, car les changements dans une couche externe n'affectent pas les couches internes critiques.

Application à Spring Boot : Structuration d'un Backend Propre

Pour un backend Spring Boot, l'application de la Clean Architecture se traduit par une organisation méticuleuse des packages et des modules. Un développeur Full Stack expert comme Laty Gueye Samba veillera à ce que le code soit structuré pour refléter les couches architecturales, garantissant ainsi une forte maintenabilité, essentielle pour des projets à long terme.

Voici une proposition de structure de packages pour un projet Spring Boot respectant la Clean Architecture :


src/main/java/com/latygueyesamba/myapp/
├── domain/
│   ├── model/         // Entités métier (Entities), classes pures
│   ├── port/          // Interfaces pour les Use Cases (inbound) et Gateways (outbound)
│   │   ├── in/        // Interfaces implémentées par les Use Cases (e.g., UserService)
│   │   └── out/       // Interfaces implémentées par les Adaptateurs d'Infrastructure (e.g., UserRepository)
├── application/
│   └── service/       // Implémentations des Use Cases (services métier)
├── infrastructure/
│   ├── adapter/
│   │   ├── in/        // Adaptateurs d'entrée (Contrôleurs REST, implémentent les ports in)
│   │   └── out/       // Adaptateurs de sortie (Implémentations des dépôts JPA, clients HTTP, implémentent les ports out)
│   ├── configuration/ // Configurations Spring
│   ├── persistence/   // Entités JPA (Data Transfer Objects), Mappers
│   └── web/           // DTOs spécifiques à l'API REST
└── MyappApplication.java

Exemple de Code (Partie Backend - Spring Boot) :

1. Domaine (domain/port/out/UserRepository.java) : Interface pure, indépendante de Spring.


package com.latygueyesamba.myapp.domain.port.out;

import com.latygueyesamba.myapp.domain.model.User;
import java.util.Optional;

public interface UserRepository {
    User save(User user);
    Optional<User> findById(Long id);
    // ... autres opérations
}

2. Cas d'Utilisation (application/service/UserService.java) : Implémente la logique métier.


package com.latygueyesamba.myapp.application.service;

import com.latygueyesamba.myapp.domain.model.User;
import com.latygueyesamba.myapp.domain.port.in.CreateUserUseCase;
import com.latygueyesamba.myapp.domain.port.out.UserRepository;

public class UserService implements CreateUserUseCase {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public User createUser(User user) {
        // Logique métier : validation, enrichissement, etc.
        return userRepository.save(user);
    }
}

3. Infrastructure (infrastructure/adapter/out/JpaUserRepositoryAdapter.java) : Implémentation du dépôt avec Spring Data JPA.


package com.latygueyesamba.myapp.infrastructure.adapter.out;

import com.latygueyesamba.myapp.domain.model.User;
import com.latygueyesamba.myapp.domain.port.out.UserRepository;
import com.latygueyesamba.myapp.infrastructure.persistence.entity.UserJpaEntity;
import com.latygueyesamba.myapp.infrastructure.persistence.repository.SpringDataUserJpaRepository;
import org.springframework.stereotype.Component;

@Component
public class JpaUserRepositoryAdapter implements UserRepository {

    private final SpringDataUserJpaRepository springDataUserJpaRepository;

    public JpaUserRepositoryAdapter(SpringDataUserJpaRepository springDataUserJpaRepository) {
        this.springDataUserJpaRepository = springDataUserJpaRepository;
    }

    @Override
    public User save(User user) {
        UserJpaEntity entity = UserJpaEntity.fromDomain(user); // Mapper Domain -> JPA
        UserJpaEntity savedEntity = springDataUserJpaRepository.save(entity);
        return savedEntity.toDomain(); // Mapper JPA -> Domain
    }

    @Override
    public Optional<User> findById(Long id) {
        return springDataUserJpaRepository.findById(id)
                .map(UserJpaEntity::toDomain);
    }
}

Intégration avec Angular : Une Architecture Côté Client Propre

Les principes de la Clean Architecture peuvent être appliqués avec succès aux applications Angular pour créer une architecture frontend modulaire et testable. L'objectif est de découpler la logique métier de l'UI et des détails d'implémentation de l'API, ce qui est crucial pour les applications complexes de gestion hospitalière ou de gestion des risques.

Voici une approche pour structurer un projet Angular :

  • Domain : Contient les interfaces de vos entités métier (modèles) et les contrats pour les services.
  • Application : Contient la logique d'orchestration (e.g., services de façade, gestion d'état NgRx) qui utilise les cas d'utilisation.
  • Infrastructure / Data : Contient les implémentations des services API (HTTP client) et les mappers pour convertir les DTOs API en modèles de domaine.
  • UI / Presentation : Contient les composants Angular (présentationnels et conteneurs) qui interagissent avec les services de l'application.

Exemple de Code (Partie Frontend - Angular) :

1. Domaine (src/app/domain/user.model.ts) : Définition du modèle métier.


export interface User {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
}

2. Infrastructure/Data (src/app/data/user-api.service.ts) : Service d'appel API, mappant les données brutes.


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { User } from '../domain/user.model'; // Import du modèle de domaine

interface UserDto { // DTO venant de l'API
  id: string;
  prenom: string;
  nom: string;
  emailContact: string;
}

@Injectable({
  providedIn: 'root'
})
export class UserApiService {
  private apiUrl = 'http://localhost:8080/api/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<UserDto[]>(this.apiUrl).pipe(
      map(dtos => dtos.map(this.mapUserDtoToUser))
    );
  }

  private mapUserDtoToUser(dto: UserDto): User {
    return {
      id: dto.id,
      firstName: dto.prenom,
      lastName: dto.nom,
      email: dto.emailContact
    };
  }
}

3. Application (src/app/application/user.facade.ts) : Facade qui orchestre les interactions avec l'API et gère l'état.


import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { User } from '../domain/user.model';
import { UserApiService } from '../data/user-api.service';

@Injectable({
  providedIn: 'root'
})
export class UserFacade {
  private usersSubject = new BehaviorSubject<User[]>([]);
  users$: Observable<User[]> = this.usersSubject.asObservable();

  constructor(private userApiService: UserApiService) {}

  loadUsers(): void {
    this.userApiService.getUsers().subscribe(
      users => this.usersSubject.next(users),
      error => console.error('Failed to load users', error)
    );
  }

  // Autres méthodes métier (e.g., addUser, updateUser, deleteUser)
}

4. Présentation (src/app/presentation/user-list/user-list.component.ts) : Composant qui utilise la Facade pour afficher les données.


import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { User } from '../../domain/user.model';
import { UserFacade } from '../../application/user.facade';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent implements OnInit {
  users$: Observable<User[]>;

  constructor(private userFacade: UserFacade) {}

  ngOnInit(): void {
    this.users$ = this.userFacade.users$;
    this.userFacade.loadUsers();
  }
}

Point de vue : développeur full stack à Dakar

Pour un développeur Full Stack expert en Java Spring Boot et Angular, travaillant sur des systèmes comme les applications de gestion des risques ou les systèmes ERP pour des organisations à Dakar et au-delà, la maîtrise de la Clean Architecture représente un avantage concurrentiel réel sur le marché technologique africain, en pleine expansion. Elle permet de délivrer des solutions d'une qualité supérieure, aptes à évoluer avec les besoins du marché et à intégrer de nouvelles technologies sans refonte majeure. La clarté et la testabilité offertes par cette architecture sont des atouts précieux pour la collaboration en équipe et la maintenance à long terme.

Conclusion

L'adoption de la Clean Architecture pour les applications Spring Boot et Angular est une stratégie puissante pour construire des systèmes maintenables, testables et indépendants. Elle promeut une séparation claire des préoccupations, protégeant la logique métier essentielle des caprices des frameworks et des détails d'implémentation.

Pour des développeurs Full Stack comme Laty Gueye Samba, expert en Java Spring Boot et Angular basé à Dakar, l'investissement dans cette architecture n'est pas seulement une question de bonnes pratiques, mais une démarche stratégique pour créer des logiciels de haute qualité, capables de résister à l'épreuve du temps et d'évoluer avec les besoins complexes du marché. C'est la garantie d'une architecture logicielle solide et pérenne.

Pour approfondir vos connaissances sur la Clean Architecture, il est recommandé de consulter les ressources officielles :

À propos de l'auteur

Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.

Contact : latygueyesamba@gmail.com  |  Dakar, Sénégal