Salta al contenuto principale

04 - Architettura Backend e Frontend con Claude

Progetta architetture con Claude: Hexagonal Architecture Spring Boot, feature-based Angular, API contract, error handling e trade-off analysis.

Cosa ne pensi di questo articolo?

04 - Architettura Backend e Frontend con Claude
ClaudeFoto:Matheus BertelliPexels

In Breve

Progetta architetture con Claude: Hexagonal Architecture Spring Boot, feature-based Angular, API contract, error handling e trade-off analysis.

Architettura con Claude: Backend e Frontend

La progettazione dell'architettura è uno dei momenti in cui Claude eccelle maggiormente. Grazie al suo reasoning trasparente, Claude può aiutarti a esplorare trade-off, confrontare pattern architetturali e prendere decisioni informate che influenzeranno l'intero ciclo di vita del progetto.

In questo articolo vedremo come usare Claude per progettare architetture backend con Spring Boot e frontend con Angular, applicando le best practices di Solution Architecture.

Cosa Imparerai

  • Usare Claude per decisioni architetturali con trade-off
  • Progettare backend Spring Boot con Clean/Hexagonal Architecture
  • Strutturare frontend Angular con pattern scalabili
  • Definire API contract tra frontend e backend
  • Gestire versioning ed error handling

Il Ruolo di Claude nell'Architettura

Claude può assumere il ruolo di Solution Architect è guidarti attraverso decisioni complesse. La chiave è fornire contesto sufficiente e chiedere esplicitamente l'analisi dei trade-off.

System Prompt: Solution Architect
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

Architettura Backend: Spring Boot

Pattern Architetturali Consigliati

Confronto Pattern Backend

Pattern Complessità Quando Usarlo Trade-off
Layered (MVC) Bassa CRUD semplici, MVP Meno flessibile, accoppiamento alto
Clean Architecture Media Business logic complessa Più codice, curva apprendimento
Hexagonal Media-Alta Integrazioni multiple, testabilità Overhead iniziale, ports/adapters
CQRS Alta Read/write asimmetrici, scalabilità Complessità infrastruttura

Esempio: Hexagonal Architecture con Spring Boot

Prompt per Design Hexagonal
<role>Sei un Senior Backend Architect specializzato in Spring Boot</role>

<context>
Progetto: Order Management per e-commerce
Stack: Spring Boot 3.2, PostgreSQL, Redis, Kafka
Team: 4 developers mid-level
Requisiti:
- CRUD ordini con stati complessi
- Integrazione payment gateway (Stripe)
- Notifiche async (email, push)
- Audit trail completo
</context>

<task>
Progetta una Hexagonal Architecture per l'Order Service.
Includi:
1. Struttura package completa
2. Definizione Ports (inbound/outbound)
3. Adapters per ogni integration
4. Use Cases principali
</task>

<output_format>
- Diagramma ASCII o Mermaid
- Struttura cartelle
- Interfacce chiave con signature
- Spiegazione delle scelte
</output_format>
Struttura Hexagonal Spring Boot
order-service/
├── src/main/java/com/company/orders/
│   ├── domain/                     # Core Business Logic
│   │   ├── model/
│   │   │   ├── Order.java          # Aggregate Root
│   │   │   ├── OrderItem.java
│   │   │   ├── OrderStatus.java    # Enum stati
│   │   │   └── 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/                     # Spring Configuration
│       ├── BeanConfiguration.java
│       ├── PersistenceConfig.java
│       └── KafkaConfig.java

Interfacce Chiave (Ports)

Inbound Port - CreateOrderUseCase.java
public interface CreateOrderUseCase {{ '{' }}

    /**
     * Crea un nuovo ordine validando disponibilità prodotti
     * e iniziando il processo di pagamento.
     *
     * @param command contiene items, customer, payment info
     * @return OrderId dell'ordine creato
     * @throws InsufficientStockException se prodotti non disponibili
     * @throws PaymentInitializationException se payment fallisce
     */
    OrderId execute(CreateOrderCommand command);
{{ '}' }}

public record CreateOrderCommand(
    CustomerId customerId,
    List<OrderItemCommand> items,
    ShippingAddress address,
    PaymentMethod paymentMethod
) {{ '{' }}{{ '}' }}
Outbound Port - PaymentGateway.java
public interface PaymentGateway {{ '{' }}

    /**
     * Inizia una transazione di pagamento.
     * Non completa il pagamento, solo lo inizializza.
     *
     * @param request dati pagamento
     * @return PaymentIntent con clientSecret per frontend
     */
    PaymentIntent initializePayment(PaymentRequest request);

    /**
     * Conferma un pagamento già autorizzato.
     *
     * @param paymentIntentId ID del payment intent
     * @return risultato conferma
     */
    PaymentConfirmation confirmPayment(String paymentIntentId);

    /**
     * Esegue refund parziale o totale.
     */
    RefundResult refund(String paymentIntentId, Money amount);
{{ '}' }}

Architettura Frontend: Angular

Pattern Consigliati per Angular

Pattern Frontend Angular

Pattern Complessità Quando Usarlo
Feature-based Bassa Progetti piccoli/medi, team singolo
Smart/Dumb Components Media Riusabilita, testabilità
Signals + Services Media State management moderno, Angular 17+
NgRx Alta Enterprise, stato complesso, debugging avanzato

Esempio: Feature-based con Signals

Struttura Angular Feature-based
src/app/
├── core/                           # Servizi 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/                         # Componenti riutilizzabili
│   ├── components/
│   │   ├── button/
│   │   ├── modal/
│   │   ├── table/
│   │   └── form-field/
│   ├── pipes/
│   │   └── currency-format.pipe.ts
│   └── directives/
│       └── click-outside.directive.ts
│
├── features/                       # Feature modules
│   ├── 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 con Signals

order.service.ts - State Management con Signals
@Injectable({{ '{' }} providedIn: 'root' {{ '}' }})
export class OrderService {{ '{' }}
  private http = inject(HttpClient);

  // State
  private ordersSignal = signal<Order[]>([]);
  private loadingSignal = signal(false);
  private errorSignal = signal<string | null>(null);
  private selectedOrderSignal = signal<Order | null>(null);

  // Public readonly signals
  readonly orders = this.ordersSignal.asReadonly();
  readonly loading = this.loadingSignal.asReadonly();
  readonly error = this.errorSignal.asReadonly();
  readonly selectedOrder = this.selectedOrderSignal.asReadonly();

  // Computed signals
  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('Errore nel caricamento ordini');
        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);
  {{ '}' }}
{{ '}' }}

API Contract: Frontend-Backend

Prompt per Design API Contract
<task>
Definisci l'API contract REST per la gestione ordini.

Requisiti:
- RESTful con risorse annidate dove appropriato
- Paginazione per liste
- Error response standardizzate
- Versioning nell'URL (/api/v1/)
- HATEOAS links (opzionale)
</task>

<output_format>
Per ogni endpoint:
- Metodo HTTP + Path
- Request body (se POST/PUT)
- Response body con codici HTTP
- Esempi JSON
</output_format>
API Contract - Orders
## 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": "Via Roma 1",
    "city": "Milano",
    "zipCode": "20100"
  {{ '}' }},
  "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
  {{ '}' }}
{{ '}' }}

Error Handling Standardizzato

Error Response Format (RFC 7807)
// 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);
    {{ '}' }}
{{ '}' }}
Frontend: Error Interceptor
// 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:
          // Redirect to 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 Architetturale

Checklist Prima di Iniziare lo Sviluppo

  • Pattern architetturale backend definito e documentato
  • Struttura cartelle frontend/backend concordata
  • API contract documentato con esempi
  • Error handling standardizzato (RFC 7807)
  • Strategia di versioning API definita
  • Convenzioni naming concordate
  • Decisioni documentate in ADR

Conclusione e Prossimi Passi

In questo articolo abbiamo visto come usare Claude per progettare architetture solide per backend Spring Boot e frontend Angular. Abbiamo esplorato:

  • Pattern architetturali e trade-off
  • Hexagonal Architecture con Spring Boot
  • Feature-based structure con Angular e Signals
  • API contract design
  • Error handling standardizzato

Nel prossimo articolo, approfondiremo la struttura del codice: organizzazione cartelle, naming conventions, separation of concerns e gestione configurazioni.

Punti Chiave da Ricordare

  • Trade-off first: Chiedi sempre a Claude di analizzare pro/contro
  • Hexagonal per integrazioni: Isola business logic da dettagli tecnici
  • Signals per Angular 17+: State management semplice e reattivo
  • API contract: Definiscilo prima di implementare
  • Documenta le decisioni: Usa ADR per tracciare le scelte

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti