Retour aux articles

Appliquer la Clean Architecture à un projet Full Stack Spring Boot et Angular

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

Dans le monde complexe du développement logiciel, la recherche de structures architecturales robustes et pérennes est une quête constante. La Clean Architecture, popularisée par Robert C. Martin (Uncle Bob), se présente comme une solution élégante pour bâtir des systèmes maintenables, testables et indépendants des détails d'implémentation. Son objectif est de séparer clairement les préoccupations, en plaçant les règles métier au cœur de l'application.

Appliquer cette architecture à un projet Full Stack, qui combine un backend comme Spring Boot et un frontend tel qu'Angular, peut sembler un défi. Cependant, les principes fondamentaux de la Clean Architecture s'adaptent remarquablement bien, offrant une clarté et une flexibilité inestimables pour les applications de grande envergure. L'expertise d'un Développeur Full Stack Java Spring Boot + Angular, tel que Laty Gueye Samba basé à Dakar, Sénégal, met en lumière l'importance de ces pratiques pour des projets complexes.

Cet article explorera comment la Clean Architecture peut être implémentée dans un écosystème Full Stack, en détaillant son application tant côté serveur avec Spring Boot que côté client avec Angular, afin d'assurer une cohérence architecturale et une évolutivité à long terme. La maîtrise de ces techniques est essentielle pour tout Expert Java Spring Boot Angular cherchant à construire des solutions robustes.

Principes Fondamentaux de la Clean Architecture

La Clean Architecture s'articule autour de cercles concentriques, chacun représentant un niveau de politique logicielle. Les dépendances ne peuvent se déplacer que vers l'intérieur, jamais vers l'extérieur. Cela signifie que les couches extérieures dépendent des couches intérieures, mais jamais l'inverse. Les quatre couches principales sont :

  • Entities (Entités) : Ce sont les règles métier de l'entreprise. Elles encapsulent les données et les règles opérationnelles les plus générales et de haut niveau. Elles devraient être indépendantes de l'application spécifique et du framework.
  • Use Cases (Cas d'Utilisation) : 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éclenchent les changements d'état des entités.
  • Interface Adapters (Adaptateurs d'Interface) : Cette couche convertit les données des couches internes au format requis par les couches externes, et vice-versa. Elle inclut les contrôleurs REST, les passerelles de base de données, les présentateurs et les vues.
  • Frameworks & Drivers (Frameworks et Pilotes) : La couche la plus externe, elle contient les détails d'implémentation tels que la base de données, le framework web (Spring Boot, Angular), l'interface utilisateur, etc. Ces éléments sont interchangeables et ne devraient pas impacter les couches internes.

Ce modèle garantit que les changements dans la base de données, le framework UI ou même le framework web n'affectent pas les règles métier fondamentales du système. C'est un atout majeur pour la maintenance et l'évolution des projets, comme ceux sur lesquels Laty Gueye Samba, Développeur Full Stack Dakar Sénégal, travaille fréquemment.

Implémentation Backend avec Spring Boot

L'application de la Clean Architecture à un backend Spring Boot implique une structuration claire du projet. Voici une approche courante :

1. Domain (Entités)

Cette couche contient les objets métier purs (POJO) qui représentent le cœur de l'application et ses règles. Elle est totalement indépendante de Spring.


// src/main/java/com/laty/cleanarch/domain/model/User.java
package com.laty.cleanarch.domain.model;

public class User {
    private Long id;
    private String username;
    private String email;

    // Getters, Setters, Constructor
    // Business rules related to User state (e.g., validate email format)
}

2. Application (Cas d'Utilisation)

Cette couche définit les interfaces pour les opérations que l'application peut effectuer et contient l'implémentation de ces opérations (les services). Elle est au cœur de la logique métier spécifique à l'application. Elle communique avec la couche Domain.


// src/main/java/com/laty/cleanarch/application/port/in/CreateUserUseCase.java
package com.laty.cleanarch.application.port.in;

import com.laty.cleanarch.domain.model.User;

public interface CreateUserUseCase {
    User createUser(CreateUserCommand command);

    class CreateUserCommand {
        private String username;
        private String email;
        // Getters, Setters, Constructor
    }
}

// src/main/java/com/laty/cleanarch/application/port/out/UserRepositoryPort.java
package com.laty.cleanarch.application.port.out;

import com.laty.cleanarch.domain.model.User;
import java.util.Optional;

public interface UserRepositoryPort {
    User save(User user);
    Optional<User> findByUsername(String username);
}

// src/main/java/com/laty/cleanarch/application/service/UserService.java
package com.laty.cleanarch.application.service;

import com.laty.cleanarch.application.port.in.CreateUserUseCase;
import com.laty.cleanarch.application.port.out.UserRepositoryPort;
import com.laty.cleanarch.domain.model.User;
import org.springframework.stereotype.Service; // Note: Service annotation is part of Spring, but implementation doesn't directly depend on Spring internals

@Service
public class UserService implements CreateUserUseCase {
    private final UserRepositoryPort userRepositoryPort;

    public UserService(UserRepositoryPort userRepositoryPort) {
        this.userRepositoryPort = userRepositoryPort;
    }

    @Override
    public User createUser(CreateUserCommand command) {
        // Business logic for user creation
        User newUser = new User();
        newUser.setUsername(command.getUsername());
        newUser.setEmail(command.getEmail());
        return userRepositoryPort.save(newUser);
    }
}

3. Infrastructure (Adaptateurs d'Interface & Frameworks)

Cette couche contient les adaptateurs qui connectent les couches internes aux détails d'implémentation externes, comme la base de données, le web (REST API), etc. C'est ici que Spring Boot joue son rôle le plus visible.


// src/main/java/com/laty/cleanarch/infrastructure/adapter/persistence/UserRepositoryAdapter.java
package com.laty.cleanarch.infrastructure.adapter.persistence;

import com.laty.cleanarch.application.port.out.UserRepositoryPort;
import com.laty.cleanarch.domain.model.User;
import org.springframework.stereotype.Component;
import java.util.Optional;

@Component
public class UserRepositoryAdapter implements UserRepositoryPort {
    // Inject actual JPA Repository here
    // private final JpaUserRepository jpaUserRepository;

    @Override
    public User save(User user) {
        // Map domain User to JPA Entity and save
        // return jpaUserRepository.save(UserJpaEntityMapper.toEntity(user));
        return user; // Simplified for example
    }

    @Override
    public Optional<User> findByUsername(String username) {
        // Map JPA Entity to domain User
        return Optional.empty(); // Simplified for example
    }
}

// src/main/java/com/laty/cleanarch/infrastructure/adapter/web/UserController.java
package com.laty.cleanarch.infrastructure.adapter.web;

import com.laty.cleanarch.application.port.in.CreateUserUseCase;
import com.laty.cleanarch.domain.model.User;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {
    private final CreateUserUseCase createUserUseCase;

    public UserController(CreateUserUseCase createUserUseCase) {
        this.createUserUseCase = createUserUseCase;
    }

    @PostMapping
    public User createUser(@RequestBody CreateUserUseCase.CreateUserCommand command) {
        return createUserUseCase.createUser(command);
    }
}

Cette structure permet de changer la base de données (de SQL à NoSQL) ou le framework web sans impacter les couches domain et application, un avantage considérable pour la flexibilité des projets de gestion hospitalière ou de systèmes ERP.

Implémentation Frontend avec Angular

Sur le frontend, la Clean Architecture vise également à séparer les préoccupations, en isolant la logique métier et les cas d'utilisation de l'interface utilisateur et des détails techniques comme l'accès aux API. Un développeur Full Stack Java Spring Boot + Angular peut appliquer ces concepts pour un frontend robuste.

1. Domain

Contient les interfaces (types TypeScript) qui représentent les entités métier de l'application frontend. Ces types sont indépendants de la façon dont les données sont affichées ou récupérées.


// src/app/core/domain/user.model.ts
export interface User {
  id: number;
  username: string;
  email: string;
}

2. Application (Use Cases/Services)

Cette couche contient les services Angular qui encapsulent la logique métier du frontend. Ils définissent les opérations que l'application peut effectuer, et utilisent des "ports" (interfaces TypeScript) pour communiquer avec la couche d'infrastructure (par exemple, des adaptateurs API).


// src/app/core/ports/user.port.ts (Output Port)
import { Observable } from 'rxjs';
import { User } from '../domain/user.model';

export interface UserPort {
  getUsers(): Observable<User[]>;
  createUser(user: Omit<User, 'id'>): Observable<User>;
}

// src/app/core/usecases/user.service.ts (Input Port implementation / Application Service)
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { User } from '../domain/user.model';
import { UserPort } from '../ports/user.port';

@Injectable({
  providedIn: 'root'
})
export class UserUseCase {
  constructor(private userPort: UserPort) {}

  getAllUsers(): Observable<User[]> {
    // Business logic related to fetching users, if any (e.g., caching, filtering)
    return this.userPort.getUsers();
  }

  addUser(user: Omit<User, 'id'>): Observable<User> {
    // Business logic related to adding a user
    return this.userPort.createUser(user);
  }
}

3. Infrastructure (Adaptateurs/Presentation)

Cette couche gère les détails techniques comme l'interaction avec l'API REST via HttpClient et la présentation de l'interface utilisateur (composants Angular). Les composants interagissent avec les UserUseCase, qui eux-mêmes utilisent un adaptateur pour l'API.


// src/app/infrastructure/adapters/user-api.adapter.ts (Input Port implementation)
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../../core/domain/user.model';
import { UserPort } from '../../core/ports/user.port';
import { environment } from '../../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class UserApiAdapter implements UserPort {
  private apiUrl = `${environment.apiUrl}/users`;

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }
}

// src/app/features/user/user-list/user-list.component.ts (Presentation Layer)
import { Component, OnInit } from '@angular/core';
import { UserUseCase } from '../../../core/usecases/user.service';
import { User } from '../../../core/domain/user.model';

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

  constructor(private userUseCase: UserUseCase) {}

  ngOnInit(): void {
    this.userUseCase.getAllUsers().subscribe(data => {
      this.users = data;
    });
  }

  // Method to add a user, calling userUseCase.addUser()
}

Cette approche permet de tester la logique métier (UserUseCase) indépendamment de l'API réelle ou de l'interface utilisateur, en mockant simplement le UserPort. Cela facilite grandement le développement et la maintenance dans des applications métier complexes, une expertise reconnue de Laty Gueye Samba.

Point de vue : développeur full stack à Dakar

Pour un développeur travaillant sur des systèmes comme des applications de gestion des risques ou de gestion hospitalière, la maîtrise de la Clean Architecture représente un avantage concurrentiel réel sur le marché technologique africain, en pleine expansion.

Conclusion

L'application de la Clean Architecture à un projet Full Stack Spring Boot et Angular est une stratégie puissante pour construire des applications robustes, flexibles et faciles à maintenir. En séparant clairement les responsabilités et en respectant la règle de dépendance, les équipes de développement peuvent garantir que les règles métier cruciales restent indépendantes des détails d'implémentation changeants.

Cette approche, bien que nécessitant un investissement initial en termes de conception, rapporte des dividendes importants sur le long terme, notamment en matière de testabilité, de scalabilité et de facilité d'évolution. Pour un Développeur Full Stack Java Spring Boot + Angular comme Laty Gueye Samba à Dakar, Sénégal, l'intégration de ces principes architecturaux est la clé pour livrer des solutions de haute qualité adaptées aux besoins spécifiques du marché, qu'il s'agisse de systèmes ERP ou d'applications de gestion des risques. La Clean Architecture Spring Boot Angular est une compétence précieuse pour tout Expert Java Spring Boot Angular.

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

  • Le blog de Robert C. Martin : The Clean Architecture
  • Le livre "Clean Architecture: A Craftsman's Guide to Software Structure and Design" par Robert C. Martin.

À 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