Retour aux articles

Concevoir une application Full Stack robuste avec Clean Architecture (Spring Boot & Angular)

Concevoir une application Full Stack robuste avec Clean Architecture (Spring Boot & Angular) | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular

Dans le monde du développement logiciel moderne, la création d'applications robustes, maintenables et évolutives est un défi constant. Les développeurs Full Stack, en particulier ceux travaillant sur des piles technologiques comme Java Spring Boot pour le backend et Angular pour le frontend, cherchent des approches architecturales qui peuvent simplifier la complexité et garantir la qualité sur le long terme. C'est dans ce contexte que la Clean Architecture se présente comme une solution puissante et éprouvée.

La Clean Architecture, popularisée par Robert C. Martin (Uncle Bob), propose une approche systématique pour organiser le code en couches distinctes et indépendantes. Cette méthode favorise une séparation claire des préoccupations, rendant les applications plus faciles à tester, à comprendre et à adapter aux changements futurs. Pour un développeur Full Stack à Dakar, spécialisé en Java Spring Boot et Angular, l'adoption de cette architecture logicielle peut transformer la manière dont sont conçues les applications, depuis les systèmes de gestion hospitalière jusqu'aux applications métier complexes.

Les principes fondamentaux de la Clean Architecture

Au cœur de la Clean Architecture se trouve la "Dependency Rule", une règle stricte stipulant que les dépendances doivent toujours pointer vers l'intérieur, c'est-à-dire des couches extérieures vers les couches intérieures. Cela signifie que le code des couches intérieures ne doit avoir aucune connaissance des couches extérieures. L'objectif est de rendre le cœur de l'application – les règles métier – indépendant de l'UI, de la base de données, des frameworks et de toute autre infrastructure externe.

Les couches concentriques typiques de la Clean Architecture incluent :

  • Entities (Entités) : Les règles métier les plus générales et de haut niveau. Elles encapsulent les règles qui ne changent pas quand l'UI, la base de données ou les composants externes changent.
  • Use Cases (Cas d'utilisation) : Les règles métier spécifiques à l'application. Ils orchestrent le flux de données vers et depuis les Entités et dirigent les Entités pour accomplir les objectifs métier du système.
  • Interface Adapters (Adaptateurs d'interface) : Cette couche convertit les données des cas d'utilisation et des entités en un format adapté à la couche extérieure suivante, comme les contrôleurs ou les présentateurs. C'est ici que l'on trouve les contrôleurs MVC, les passerelles (gateways) de base de données, etc.
  • Frameworks & Drivers (Frameworks et pilotes) : La couche la plus externe, composée de tous les outils et frameworks, comme la base de données, le framework web (Spring Boot, Angular), l'UI, etc. Le code de cette couche ne doit avoir aucune influence sur les couches internes.

Implémentation de la Clean Architecture avec Spring Boot (Backend)

Pour le backend, Laty Gueye Samba, expert en Java Spring Boot, recommande une structuration des projets qui reflète les principes de la Clean Architecture. Cela permet de construire une API RESTful solide et maintenable. Voici une approche courante :


// Couche 1: Domain (Entities & Use Cases Interfaces)
package com.latygueyesamba.domain.model;

public class User {
    private Long id;
    private String username;
    private String email;
    // Getters et Setters
}

package com.latygueyesamba.domain.usecase;

public interface UserService {
    User createUser(User user);
    User findUserById(Long id);
    // Autres méthodes métier
}

// Couche 2: Application (Use Cases Implementations & DTOs)
package com.latygueyesamba.application.service;

import com.latygueyesamba.domain.model.User;
import com.latygueyesamba.domain.usecase.UserService;
import com.latygueyesamba.application.port.out.UserRepositoryPort; // Port pour l'infrastructure

public class UserServiceImpl implements UserService {

    private final UserRepositoryPort userRepositoryPort;

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

    @Override
    public User createUser(User user) {
        // Logique métier spécifique, validations
        return userRepositoryPort.save(user);
    }

    @Override
    public User findUserById(Long id) {
        return userRepositoryPort.findById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }
}

// Couche 3: Infrastructure (Controllers, Repositories Implementations)
package com.latygueyesamba.infrastructure.adapter.in.web;

import com.latygueyesamba.application.dto.UserDTO;
import com.latygueyesamba.domain.model.User;
import com.latygueyesamba.domain.usecase.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO userDto) {
        User user = new User(userDto.getUsername(), userDto.getEmail()); // Mapper DTO vers Domain Model
        User createdUser = userService.createUser(user);
        return ResponseEntity.ok(new UserDTO(createdUser.getId(), createdUser.getUsername(), createdUser.getEmail()));
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
        User user = userService.findUserById(id);
        return ResponseEntity.ok(new UserDTO(user.getId(), user.getUsername(), user.getEmail()));
    }
}

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

import com.latygueyesamba.application.port.out.UserRepositoryPort;
import com.latygueyesamba.domain.model.User;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public class JpaUserRepositoryAdapter implements UserRepositoryPort { // Implémentation du port

    // Injection de dépendance du repository JPA Spring Data
    private final SpringDataUserRepository springDataUserRepository;

    public JpaUserRepositoryAdapter(SpringDataUserRepository springDataUserRepository) {
        this.springDataUserRepository = springDataUserRepository;
    }

    @Override
    public User save(User user) {
        // Mapper Domain Model vers Entity JPA
        return springDataUserRepository.save(user);
    }

    @Override
    public Optional<User> findById(Long id) {
        // Mapper Entity JPA vers Domain Model
        return springDataUserRepository.findById(id);
    }
}

Cette structure garantit que les règles métier (domain) restent indépendantes des détails techniques d'implémentation (infrastructure).

Intégration de la Clean Architecture avec Angular (Frontend)

Bien que la Clean Architecture soit souvent associée au backend, ses principes peuvent être appliqués efficacement au frontend Angular pour créer des applications web robustes et maintenables. L'idée est de séparer la logique métier de l'interface utilisateur et des détails d'implémentation des APIs.

  • Domain (Core) : Contient les modèles de données (interfaces ou classes) et les règles métier pures. Ces modèles sont indépendants de l'API REST et représentent la vérité métier de l'application.
  • Application (Use Cases / Services) : Gère la logique spécifique à l'application. Cela inclut des services qui coordonnent les interactions entre les composants et le domaine, et qui peuvent utiliser des "ports" pour communiquer avec l'infrastructure. Par exemple, un UserService qui appelle un UserRepository.
  • Infrastructure (Adapters) : Contient les implémentations concrètes des ports définis dans la couche Application. Ce sont les services qui interagissent directement avec l'API REST (via HttpClient) ou le stockage local. Ils adaptent les données de l'API aux modèles du domaine.
  • Presentation (UI) : La couche la plus externe, composée des composants Angular. Ceux-ci ne devraient pas contenir de logique métier complexe, mais plutôt interagir avec les services de la couche Application pour afficher les données et gérer les interactions utilisateur.

// Couche 1: Domain (Core)
// src/app/core/models/user.model.ts
export interface User {
  id: number;
  username: string;
  email: string;
}

// Couche 2: Application (Services/Use Cases)
// src/app/application/use-cases/user.service.ts
import { Injectable } from '@angular/core';
import { User } from '../../core/models/user.model';
import { UserRepositoryPort } from '../../application/ports/user-repository.port';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class UserUseCase { // Renommé en UserUseCase pour plus de clarté
  constructor(private userRepository: UserRepositoryPort) {}

  getUsers(): Observable<User[]> {
    return this.userRepository.getAll();
  }

  getUserById(id: number): Observable<User> {
    return this.userRepository.getById(id);
  }

  createUser(user: User): Observable<User> {
    return this.userRepository.save(user);
  }
}

// src/app/application/ports/user-repository.port.ts (Interface)
import { Observable } from 'rxjs';
import { User } from '../../core/models/user.model';

export abstract class UserRepositoryPort {
  abstract getAll(): Observable<User[]>;
  abstract getById(id: number): Observable<User>;
  abstract save(user: User): Observable<User>;
}

// Couche 3: Infrastructure (Adapters)
// src/app/infrastructure/adapters/user-api.adapter.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../../core/models/user.model';
import { UserRepositoryPort } from '../../application/ports/user-repository.port';

@Injectable({
  providedIn: 'root'
})
export class UserApiAdapter implements UserRepositoryPort {
  private apiUrl = 'http://localhost:8080/api/users'; // Votre API Spring Boot

  constructor(private http: HttpClient) {}

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

  getById(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`);
  }

  save(user: User): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }
}

// Couche 4: Presentation (UI Components)
// src/app/presentation/user-list/user-list.component.ts
import { Component, OnInit } from '@angular/core';
import { User } from '../../core/models/user.model';
import { UserUseCase } from '../../application/use-cases/user.service'; // Utilise le UseCase

@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.getUsers().subscribe(data => {
      this.users = data;
    });
  }
}

Point de vue : développeur full stack à Dakar

Pour un développeur travaillant sur des systèmes ERP ou des applications de gestion des risques, la maîtrise de la Clean Architecture représente un avantage concurrentiel réel sur le marché technologique africain, en pleine expansion. Cela permet de livrer des solutions pérennes, un critère essentiel pour les entreprises locales et internationales investissant dans la région. Laty Gueye Samba, Développeur Full Stack à Dakar, insiste souvent sur l'importance d'une architecture solide pour la réussite des projets, qu'il s'agisse de gestion de la santé ou de systèmes financiers.

Conclusion

La Clean Architecture, appliquée à une pile technologique comme Spring Boot et Angular, offre une feuille de route claire pour construire des applications Full Stack robustes, testables et facilement adaptables aux évolutions futures. Elle permet de protéger le cœur métier de l'application des changements d'infrastructure ou de framework, garantissant ainsi sa longévité et sa valeur. Pour les développeurs Full Stack, et en particulier Laty Gueye Samba, Développeur Full Stack Dakar Sénégal et expert Java Spring Boot Angular, l'adoption de cette architecture logicielle est une étape clé vers l'excellence technique et la livraison de solutions de haute qualité.

Pour approfondir le sujet, 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