Architecture avec Claude : Backend et Frontend
La conception de l'architecture est l'un des moments où Claude excelle le plus. Grâce à son raisonnement transparent, Claude peut vous aider à explorer les trade-offs, comparer les patterns architecturaux et prendre des décisions éclairées qui influenceront tout le cycle de vie du projet.
Dans cet article, nous verrons comment utiliser Claude pour concevoir des architectures backend avec Spring Boot et frontend avec Angular, en appliquant les meilleures pratiques de Solution Architecture.
Ce que Vous Apprendrez
- Utiliser Claude pour des décisions architecturales avec trade-offs
- Concevoir un backend Spring Boot avec Clean/Hexagonal Architecture
- Structurer un frontend Angular avec des patterns scalables
- Définir des contrats API entre frontend et backend
- Gérer le versioning et la gestion des erreurs
Le Rôle de Claude dans l'Architecture
Claude peut assumer le rôle de Solution Architect et vous guider à travers des décisions complexes. La clé est de fournir suffisamment de contexte et de demander explicitement l'analyse des trade-offs.
You are a Senior Solution Architect with 15+ years of experience
in enterprise software development.
EXPERTISE:
- Microservices architecture (Spring Boot, Kubernetes)
- Frontend architecture (Angular, React, Vue)
- Event-driven systems (Kafka, RabbitMQ)
- Cloud platforms (AWS, Azure, GCP)
- DDD, Clean Architecture, Hexagonal
COMMUNICATION STYLE:
- Always explain trade-offs for every recommendation
- Use diagrams (ASCII or Mermaid) when helpful
- Cite industry standards (12-factor app, SOLID, etc.)
- Challenge assumptions when they seem problematic
CONSTRAINTS:
- Prioritize maintainability over cleverness
- Consider team size and skill level
- Focus on practical, implementable solutions
- Always address security implications
Architecture Backend : Spring Boot
Patterns Architecturaux Recommandés
Comparaison des Patterns Backend
| Pattern | Complexité | Quand l'Utiliser | Trade-off |
|---|---|---|---|
| Layered (MVC) | Faible | CRUD simples, MVP | Moins flexible, fort couplage |
| Clean Architecture | Moyenne | Logique métier complexe | Plus de code, courbe d'apprentissage |
| Hexagonal | Moyenne-Haute | Intégrations multiples, testabilité | Overhead initial, ports/adapters |
| CQRS | Haute | Lecture/écriture asymétriques, scalabilité | Complexité d'infrastructure |
Exemple : Hexagonal Architecture avec Spring Boot
<role>Tu es un Senior Backend Architect spécialisé en Spring Boot</role>
<context>
Projet : Order Management pour e-commerce
Stack : Spring Boot 3.2, PostgreSQL, Redis, Kafka
Équipe : 4 développeurs niveau intermédiaire
Exigences :
- CRUD de commandes avec des états complexes
- Intégration payment gateway (Stripe)
- Notifications asynchrones (email, push)
- Audit trail complet
</context>
<task>
Conçois une Hexagonal Architecture pour l'Order Service.
Inclus :
1. Structure complète des packages
2. Définition des Ports (inbound/outbound)
3. Adapters pour chaque intégration
4. Use Cases principaux
</task>
<output_format>
- Diagramme ASCII ou Mermaid
- Structure des dossiers
- Interfaces clés avec signature
- Explication des choix
</output_format>
order-service/
├── src/main/java/com/company/orders/
│ ├── domain/ # Core Business Logic
│ │ ├── model/
│ │ │ ├── Order.java # Aggregate Root
│ │ │ ├── OrderItem.java
│ │ │ ├── OrderStatus.java # Enum des états
│ │ │ └── Money.java # Value Object
│ │ ├── service/
│ │ │ └── OrderDomainService.java
│ │ └── event/
│ │ └── OrderEvent.java # Domain Events
│ │
│ ├── application/ # Use Cases / Ports
│ │ ├── port/
│ │ │ ├── in/ # Inbound Ports
│ │ │ │ ├── CreateOrderUseCase.java
│ │ │ │ ├── UpdateOrderStatusUseCase.java
│ │ │ │ └── GetOrderQuery.java
│ │ │ └── out/ # Outbound Ports
│ │ │ ├── OrderRepository.java
│ │ │ ├── PaymentGateway.java
│ │ │ ├── NotificationSender.java
│ │ │ └── EventPublisher.java
│ │ └── service/
│ │ └── OrderApplicationService.java
│ │
│ ├── adapter/ # Infrastructure
│ │ ├── in/ # Inbound Adapters
│ │ │ ├── web/
│ │ │ │ ├── OrderController.java
│ │ │ │ ├── dto/
│ │ │ │ │ ├── CreateOrderRequest.java
│ │ │ │ │ └── OrderResponse.java
│ │ │ │ └── mapper/
│ │ │ │ └── OrderDtoMapper.java
│ │ │ └── event/
│ │ │ └── OrderEventListener.java
│ │ └── out/ # Outbound Adapters
│ │ ├── persistence/
│ │ │ ├── OrderJpaRepository.java
│ │ │ ├── OrderPersistenceAdapter.java
│ │ │ └── entity/
│ │ │ └── OrderJpaEntity.java
│ │ ├── payment/
│ │ │ └── StripePaymentAdapter.java
│ │ ├── notification/
│ │ │ ├── EmailNotificationAdapter.java
│ │ │ └── PushNotificationAdapter.java
│ │ └── messaging/
│ │ └── KafkaEventPublisher.java
│ │
│ └── config/ # Configuration Spring
│ ├── BeanConfiguration.java
│ ├── PersistenceConfig.java
│ └── KafkaConfig.java
Interfaces Clés (Ports)
public interface CreateOrderUseCase {{ '{' }}
/**
* Crée une nouvelle commande en validant la disponibilité des produits
* et en initiant le processus de paiement.
*
* @param command contient items, customer, payment info
* @return OrderId de la commande créée
* @throws InsufficientStockException si les produits ne sont pas disponibles
* @throws PaymentInitializationException si le paiement échoue
*/
OrderId execute(CreateOrderCommand command);
{{ '}' }}
public record CreateOrderCommand(
CustomerId customerId,
List<OrderItemCommand> items,
ShippingAddress address,
PaymentMethod paymentMethod
) {{ '{' }}{{ '}' }}
public interface PaymentGateway {{ '{' }}
/**
* Initie une transaction de paiement.
* Ne complète pas le paiement, ne fait que l'initialiser.
*
* @param request données de paiement
* @return PaymentIntent avec clientSecret pour le frontend
*/
PaymentIntent initializePayment(PaymentRequest request);
/**
* Confirme un paiement déjà autorisé.
*
* @param paymentIntentId ID du payment intent
* @return résultat de la confirmation
*/
PaymentConfirmation confirmPayment(String paymentIntentId);
/**
* Effectue un remboursement partiel ou total.
*/
RefundResult refund(String paymentIntentId, Money amount);
{{ '}' }}
Architecture Frontend : Angular
Patterns Recommandés pour Angular
Patterns Frontend Angular
| Pattern | Complexité | Quand l'Utiliser |
|---|---|---|
| Feature-based | Faible | Projets petits/moyens, équipe unique |
| Smart/Dumb Components | Moyenne | Réutilisation, testabilité |
| Signals + Services | Moyenne | State management moderne, Angular 17+ |
| NgRx | Haute | Enterprise, état complexe, debugging avancé |
Exemple : Feature-based avec Signals
src/app/
├── core/ # Services singleton
│ ├── services/
│ │ ├── api.service.ts # HTTP client base
│ │ ├── auth.service.ts
│ │ └── notification.service.ts
│ ├── guards/
│ │ └── auth.guard.ts
│ ├── interceptors/
│ │ ├── auth.interceptor.ts
│ │ └── error.interceptor.ts
│ └── models/
│ └── api-response.model.ts
│
├── shared/ # Composants réutilisables
│ ├── components/
│ │ ├── button/
│ │ ├── modal/
│ │ ├── table/
│ │ └── form-field/
│ ├── pipes/
│ │ └── currency-format.pipe.ts
│ └── directives/
│ └── click-outside.directive.ts
│
├── features/ # Modules de features
│ ├── orders/
│ │ ├── components/
│ │ │ ├── order-list/ # Smart component
│ │ │ │ ├── order-list.component.ts
│ │ │ │ └── order-list.component.html
│ │ │ ├── order-card/ # Dumb component
│ │ │ │ ├── order-card.component.ts
│ │ │ │ └── order-card.component.html
│ │ │ └── order-detail/
│ │ ├── services/
│ │ │ └── order.service.ts # State + API
│ │ ├── models/
│ │ │ └── order.model.ts
│ │ └── orders.routes.ts
│ │
│ ├── products/
│ └── customers/
│
├── layouts/
│ ├── main-layout/
│ └── auth-layout/
│
└── app.routes.ts
Service avec Signals
@Injectable({{ '{' }} providedIn: 'root' {{ '}' }})
export class OrderService {{ '{' }}
private http = inject(HttpClient);
// État
private ordersSignal = signal<Order[]>([]);
private loadingSignal = signal(false);
private errorSignal = signal<string | null>(null);
private selectedOrderSignal = signal<Order | null>(null);
// Signals publiques en lecture seule
readonly orders = this.ordersSignal.asReadonly();
readonly loading = this.loadingSignal.asReadonly();
readonly error = this.errorSignal.asReadonly();
readonly selectedOrder = this.selectedOrderSignal.asReadonly();
// Signals calculées
readonly pendingOrders = computed(() =>
this.ordersSignal().filter(o => o.status === 'PENDING')
);
readonly orderStats = computed(() => ({{ '{' }}
total: this.ordersSignal().length,
pending: this.pendingOrders().length,
totalRevenue: this.ordersSignal()
.filter(o => o.status === 'COMPLETED')
.reduce((sum, o) => sum + o.total, 0)
{{ '}' }}));
loadOrders(): void {{ '{' }}
this.loadingSignal.set(true);
this.errorSignal.set(null);
this.http.get<Order[]>('/api/orders').pipe(
catchError(err => {{ '{' }}
this.errorSignal.set('Erreur lors du chargement des commandes');
return of([]);
{{ '}' }}),
finalize(() => this.loadingSignal.set(false))
).subscribe(orders => this.ordersSignal.set(orders));
{{ '}' }}
createOrder(order: CreateOrderDto): Observable<Order> {{ '{' }}
return this.http.post<Order>('/api/orders', order).pipe(
tap(newOrder => {{ '{' }}
this.ordersSignal.update(orders => [...orders, newOrder]);
{{ '}' }})
);
{{ '}' }}
selectOrder(orderId: string): void {{ '{' }}
const order = this.ordersSignal().find(o => o.id === orderId);
this.selectedOrderSignal.set(order ?? null);
{{ '}' }}
{{ '}' }}
Contrat API : Frontend-Backend
<task>
Définis le contrat API REST pour la gestion des commandes.
Exigences :
- RESTful avec des ressources imbriquées où approprié
- Pagination pour les listes
- Réponses d'erreur standardisées
- Versioning dans l'URL (/api/v1/)
- Liens HATEOAS (optionnel)
</task>
<output_format>
Pour chaque endpoint :
- Méthode HTTP + Path
- Request body (si POST/PUT)
- Response body avec codes HTTP
- Exemples JSON
</output_format>
## Orders API v1
### Create Order
POST /api/v1/orders
Content-Type: application/json
Request:
{{ '{' }}
"customerId": "cust_123",
"items": [
{{ '{' }} "productId": "prod_456", "quantity": 2 {{ '}' }}
],
"shippingAddress": {{ '{' }}
"street": "1 Rue de Rivoli",
"city": "Paris",
"zipCode": "75001"
{{ '}' }},
"paymentMethod": "CARD"
{{ '}' }}
Response 201 Created:
{{ '{' }}
"id": "ord_789",
"status": "PENDING_PAYMENT",
"total": 99.99,
"createdAt": "2025-02-01T10:00:00Z",
"_links": {{ '{' }}
"self": {{ '{' }} "href": "/api/v1/orders/ord_789" {{ '}' }},
"payment": {{ '{' }} "href": "/api/v1/orders/ord_789/payment" {{ '}' }}
{{ '}' }}
{{ '}' }}
Response 400 Bad Request:
{{ '{' }}
"type": "validation_error",
"title": "Validation Failed",
"status": 400,
"errors": [
{{ '{' }}
"field": "items",
"message": "At least one item required"
{{ '}' }}
]
{{ '}' }}
### Get Orders (Paginated)
GET /api/v1/orders?page=0&size=20&status=PENDING
Response 200 OK:
{{ '{' }}
"content": [...],
"page": {{ '{' }}
"number": 0,
"size": 20,
"totalElements": 150,
"totalPages": 8
{{ '}' }}
{{ '}' }}
Gestion des Erreurs Standardisée
// Backend: GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {{ '{' }}
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ProblemDetail> handleNotFound(
OrderNotFoundException ex) {{ '{' }}
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND,
ex.getMessage()
);
problem.setType(URI.create("/errors/order-not-found"));
problem.setTitle("Order Not Found");
problem.setProperty("orderId", ex.getOrderId());
return ResponseEntity.status(404).body(problem);
{{ '}' }}
@ExceptionHandler(PaymentFailedException.class)
public ResponseEntity<ProblemDetail> handlePaymentFailed(
PaymentFailedException ex) {{ '{' }}
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.PAYMENT_REQUIRED,
"Payment processing failed"
);
problem.setType(URI.create("/errors/payment-failed"));
problem.setProperty("reason", ex.getReason());
problem.setProperty("retryable", ex.isRetryable());
return ResponseEntity.status(402).body(problem);
{{ '}' }}
{{ '}' }}
// error.interceptor.ts
export const errorInterceptor: HttpInterceptorFn = (req, next) => {{ '{' }}
const notification = inject(NotificationService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {{ '{' }}
const problemDetail = error.error as ProblemDetail;
switch (error.status) {{ '{' }}
case 400:
notification.showError(
problemDetail.title ?? 'Validation Error'
);
break;
case 401:
// Rediriger vers le login
break;
case 404:
notification.showWarning(
problemDetail.detail ?? 'Resource not found'
);
break;
case 500:
notification.showError(
'Server error. Please try again later.'
);
break;
{{ '}' }}
return throwError(() => error);
{{ '}' }})
);
{{ '}' }};
Checklist Architecturale
Checklist Avant de Commencer le Développement
- Pattern architecturel backend défini et documenté
- Structure de dossiers frontend/backend convenue
- Contrat API documenté avec exemples
- Gestion des erreurs standardisée (RFC 7807)
- Stratégie de versioning API définie
- Conventions de naming convenues
- Décisions documentées en ADR
Conclusion et Prochaines Étapes
Dans cet article, nous avons vu comment utiliser Claude pour concevoir des architectures solides pour le backend Spring Boot et le frontend Angular. Nous avons exploré :
- Les patterns architecturaux et les trade-offs
- L'Hexagonal Architecture avec Spring Boot
- La structure feature-based avec Angular et Signals
- Le design des contrats API
- La gestion des erreurs standardisée
Dans le prochain article, nous approfondirons la structure du code : organisation des dossiers, conventions de naming, séparation des responsabilités et gestion des configurations.
Points Clés à Retenir
- Trade-off first : Demandez toujours à Claude d'analyser les pour/contre
- Hexagonal pour les intégrations : Isolez la logique métier des détails techniques
- Signals pour Angular 17+ : State management simple et réactif
- Contrat API : Définissez-le avant d'implémenter
- Documentez les décisions : Utilisez les ADR pour tracer les choix







