Retour aux articles

Appliquer Clean Architecture dans un projet Spring Boot et Angular

Appliquer Clean Architecture dans un projet Spring Boot et Angular | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular

Introduction

Ce billet explique comment appliquer la Clean Architecture dans un projet combinant Spring Boot pour le backend et Angular pour le frontend. L'objectif est de séparer clairement les responsabilités, protéger le domaine métier des détails techniques et faciliter les tests, l'évolutivité et la maintenance. L'approche présentée s'appuie sur les couches Domain, Use Cases (Application), Adapters et Infrastructure, ainsi que sur une couche Presentation pour Angular.

Principes essentiels de la Clean Architecture

Indépendance vis-à-vis des frameworks : le cœur métier ne dépend d'aucun framework.

Testabilité : les cas d'utilisation et les entités doivent être testables isolément.

Indépendance de l'UI : l'interface utilisateur peut être changée sans impacter le domaine.

Règle de dépendance : les dépendances pointent vers l'intérieur — les couches externes dépendent des couches internes, jamais l'inverse.

Architecture recommandée

Une structure de projet backend typique pourrait ressembler à :

com.example.project ├─ domain │ ├─ model │ └─ port ├─ application │ └─ usecase ├─ adapter │ ├─ inbound (web/controllers) │ └─ outbound (persistence) └─ infrastructure └─ configuration

La couche frontend Angular sépare également le modèle métier des services d'accès aux API :

src/app ├─ core (models, ports) ├─ features (components, containers) └─ adapters (api services, mappers)

Exemple de Domain (Java)

Les entités et interfaces qui représentent le domaine métier restent purs de toute dépendance Spring :

package com.example.project.domain.model; public class Customer { private String id; private String name; // getters, constructors, equals/hashCode } package com.example.project.domain.port; public interface CustomerRepositoryPort { Optional findById(String id); Customer save(Customer customer); }

Use Case / Application (Java)

Les cas d'utilisation orchestrent la logique métier en s'appuyant sur des ports (interfaces) :

package com.example.project.application.usecase; import com.example.project.domain.model.Customer; import com.example.project.domain.port.CustomerRepositoryPort; public class CreateCustomerUseCase { private final CustomerRepositoryPort repository; public CreateCustomerUseCase(CustomerRepositoryPort repository) { this.repository = repository; } public Customer execute(CreateCustomerCommand cmd) { Customer customer = new Customer(/* mapping from cmd */); return repository.save(customer); } }

Adapter et Infrastructure (Spring Boot)

Les adaptateurs implémentent les ports et utilisent Spring Data ou JDBC. La configuration des beans se situe dans l'infrastructure afin d'injecter les implémentations dans les use cases :

package com.example.project.adapter.out.persistence; @Repository public class CustomerRepositoryAdapter implements CustomerRepositoryPort { private final SpringDataCustomerRepository repo; public CustomerRepositoryAdapter(SpringDataCustomerRepository repo) { this.repo = repo; } public Optional findById(String id) { return repo.findById(id).map(entity -> entity.toDomain()); } }

La configuration lie l'implémentation au port :

@Configuration public class UseCaseConfig { @Bean public CreateCustomerUseCase createCustomerUseCase(CustomerRepositoryPort repo) { return new CreateCustomerUseCase(repo); } }

Controller REST (Adapter inbound)

Les contrôleurs exposent les APIs et appellent les use cases. Ils sont responsables des DTOs et du mapping vers le domaine :

@RestController @RequestMapping("/customers") public class CustomerController { private final CreateCustomerUseCase createCustomerUseCase; public CustomerController(CreateCustomerUseCase useCase) { this.createCustomerUseCase = useCase; } @PostMapping public ResponseEntity create(@RequestBody CreateCustomerDto dto) { Customer customer = createCustomerUseCase.execute(dto.toCommand()); return ResponseEntity.status(HttpStatus.CREATED).body(CustomerDto.fromDomain(customer)); } }

Structuration côté Angular

Le frontend doit respecter la même séparation : les modèles métiers (interfaces) et les cas d'utilisation (services) n'importent pas directement des détails HTTP. Un adaptateur API réalise le mapping entre les DTOs JSON et les modèles métier.

Exemple TypeScript

export interface Customer { id: string; name: string; } @Injectable({ providedIn: 'root' }) export class CustomerApiAdapter { constructor(private http: HttpClient) {} create(dto: CreateCustomerDto): Observable { return this.http.post('/api/customers', dto) .pipe(map(d => ({ id: d.id, name: d.name }))); } }

Tests et automatisation

Les tests unitaires ciblent les use cases et les entités sans charger Spring. Les tests d'intégration peuvent démarrer Spring et vérifier l'intégration des adapters persistence. Pour Angular, les services métiers se testent avec des mocks pour l'adapter API et les tests E2E vérifient le flux complet.

Bonnes pratiques et pièges à éviter

Respecter la règle de dépendance : éviter d'importer des classes d'infrastructure dans le domaine.

Mapper explicitement : utiliser des mappers entre DTOs et modèles métier plutôt que des conversions ad hoc.

Garder les use cases fins : chaque use case doit correspondre à une action métier cohérente.

Éviter l'anémie du domaine : privilégier des entités riches quand la logique y a sa place, et placer l'orchestration dans les use cases.

Conclusion

Appliquer la Clean Architecture dans un projet Spring Boot + Angular apporte une meilleure séparation des responsabilités, une testabilité accrue et une maintenance facilitée. En définissant clairement les ports et adaptateurs, en isolant le domaine et en centralisant la configuration dans l'infrastructure, l'équipe obtient une base solide pour l'évolution du produit.

À 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