Guida implementazione Microservizi

Guida implementazione Microservizi

Guida Implementazione Microservizi: Da Zero alla Produzione

Introduzione

L’architettura a microservizi rappresenta oggi lo standard de facto per sviluppare applicazioni enterprise scalabili e resilienti. Diversamente dalle applicazioni monolitiche, i microservizi suddividono un sistema in componenti indipendenti, deployabili separatamente, che comunicano attraverso API ben definite. Questa guida tecnica vi accompagnerà nell’implementazione pratica di un’architettura a microservizi, utilizzando Java con Spring Boot e JavaScript con Node.js, seguendo un caso d’uso reale di e-commerce fino al deployment in produzione.

Caso d’Uso: Sistema E-Commerce Semplificato – Guida implementazione Microservizi

Implementeremo tre microservizi core:

  • Order Service: gestisce gli ordini (Java/Spring Boot)
  • Inventory Service: gestisce il catalogo prodotti (Java/Spring Boot)
  • Notification Service: invia notifiche agli utenti (Node.js/Express)

Database per Service Pattern

Il pattern fondamentale dei microservizi prevede che ogni servizio possegga il proprio database, garantendo disaccoppiamento completo e autonomia deployable. Questo elimina il single point of failure e permette a ogni team di scegliere la tecnologia più adatta.

Esempio Order Service con MySQL:

// application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/orderdb
    username: orderuser
    password: orderpass
  jpa:
    hibernate:
      ddl-auto: update

// Order Entity
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String customerId;
    private String productId;
    private Integer quantity;
    private BigDecimal totalAmount;
    
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
}

Inventory Service con MongoDB:

// application.yml
spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/inventorydb

// Product Document
@Document(collection = "products")
public class Product {
    @Id
    private String id;
    private String name;
    private BigDecimal price;
    private Integer stock;
}

API Gateway Pattern

L’API Gateway funge da entry point unico per tutti i client, gestendo routing, autenticazione, rate limiting e aggregazione delle chiamate. Utilizziamo Spring Cloud Gateway.

@Configuration
public class GatewayConfig {
    
    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("order-service", r -> r
                .path("/api/orders/**")
                .filters(f -> f
                    .circuitBreaker(c -> c.setName("orderCircuitBreaker"))
                    .retry(3))
                .uri("lb://ORDER-SERVICE"))
            .route("inventory-service", r -> r
                .path("/api/products/**")
                .uri("lb://INVENTORY-SERVICE"))
            .build();
    }
}

Backend for Frontend (BFF) Pattern

Il BFF crea API dedicate per specifici client (web, mobile, IoT). Ogni frontend ha il proprio backend ottimizzato.

BFF Mobile con Node.js:

const express = require('express');
const axios = require('axios');

const app = express();
const GATEWAY_URL = 'http://localhost:8080';

// Endpoint ottimizzato per mobile
app.get('/mobile/orders/:userId', async (req, res) => {
  try {
    const orders = await axios.get(
      `${GATEWAY_URL}/api/orders/user/${req.params.userId}`
    );
    
    // Trasformazione dati per mobile (payload ridotto)
    const mobileOrders = orders.data.map(order => ({
      id: order.id,
      total: order.totalAmount,
      status: order.status
    }));
    
    res.json(mobileOrders);
  } catch (error) {
    res.status(500).json({ error: 'Service unavailable' });
  }
});

CQRS (Command Query Responsibility Segregation)

CQRS separa le operazioni di lettura da quelle di scrittura, ottimizzando performance e scalabilità. Il modello di comando gestisce gli aggiornamenti, mentre il modello di query serve viste denormalizzate.

// Command Side
@Service
public class OrderCommandService {
    
    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public Order createOrder(CreateOrderCommand command) {
        Order order = new Order();
        order.setCustomerId(command.getCustomerId());
        order.setProductId(command.getProductId());
        order.setQuantity(command.getQuantity());
        
        Order savedOrder = orderRepository.save(order);
        
        // Pubblica evento per aggiornare query side
        eventPublisher.publishEvent(
            new OrderCreatedEvent(savedOrder.getId())
        );
        
        return savedOrder;
    }
}

// Query Side - Vista ottimizzata
@Service
public class OrderQueryService {
    
    private final OrderReadRepository readRepository;
    
    public List<OrderSummaryDTO> getCustomerOrders(String customerId) {
        // Legge da database ottimizzato per query
        return readRepository.findByCustomerId(customerId);
    }
}

Event Sourcing Pattern

Event Sourcing persiste ogni cambiamento di stato come evento immutabile. L’aggregato viene ricostruito riproducendo gli eventi.

@Aggregate
public class OrderAggregate {
    
    @AggregateIdentifier
    private String orderId;
    private OrderStatus status;
    
    @CommandHandler
    public OrderAggregate(CreateOrderCommand command) {
        AggregateLifecycle.apply(
            new OrderCreatedEvent(
                command.getOrderId(),
                command.getCustomerId(),
                command.getProductId()
            )
        );
    }
    
    @EventSourcingHandler
    public void on(OrderCreatedEvent event) {
        this.orderId = event.getOrderId();
        this.status = OrderStatus.CREATED;
    }
    
    @EventSourcingHandler
    public void on(OrderConfirmedEvent event) {
        this.status = OrderStatus.CONFIRMED;
    }
}

Saga Pattern

Il Saga Pattern coordina transazioni distribuite attraverso una sequenza di transazioni locali. Ogni passo pubblica eventi che triggherano il passo successivo. In caso di fallimento, vengono eseguite transazioni compensative.

Orchestration-based Saga:

@Saga
public class OrderSaga {
    
    @Autowired
    private transient CommandGateway commandGateway;
    
    @StartSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderCreatedEvent event) {
        // Step 1: Riserva inventario
        commandGateway.send(
            new ReserveInventoryCommand(
                event.getOrderId(),
                event.getProductId(),
                event.getQuantity()
            )
        );
    }
    
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(InventoryReservedEvent event) {
        // Step 2: Processo pagamento
        commandGateway.send(
            new ProcessPaymentCommand(event.getOrderId())
        );
    }
    
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(PaymentFailedEvent event) {
        // Compensazione: rilascia inventario
        commandGateway.send(
            new ReleaseInventoryCommand(event.getOrderId())
        );
    }
    
    @EndSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderCompletedEvent event) {
        // Saga completata con successo
    }
}

Comunicazione tra Microservizi – Guida implementazione Microservizi

Sincrona con RestTemplate/WebClient:

@Service
public class OrderService {
    
    private final WebClient.Builder webClientBuilder;
    
    public OrderResponse createOrder(OrderRequest request) {
        // Chiamata sincrona a Inventory Service
        InventoryResponse inventory = webClientBuilder.build()
            .get()
            .uri("http://INVENTORY-SERVICE/api/products/{id}/stock",
                request.getProductId())
            .retrieve()
            .bodyToMono(InventoryResponse.class)
            .block();
            
        if (inventory.getStock() < request.getQuantity()) {
            throw new InsufficientStockException();
        }
        
        // Procedi con creazione ordine
    }
}

Asincrona con Kafka:

// Notification Service - Consumer Kafka
const { Kafka } = require('kafkajs');

const kafka = new Kafka({
  clientId: 'notification-service',
  brokers: ['localhost:9092']
});

const consumer = kafka.consumer({ 
  groupId: 'notification-group' 
});

const run = async () => {
  await consumer.connect();
  await consumer.subscribe({ 
    topic: 'order-events', 
    fromBeginning: true 
  });

  await consumer.run({
    eachMessage: async ({ topic, partition, message }) => {
      const event = JSON.parse(message.value.toString());
      
      if (event.type === 'ORDER_CONFIRMED') {
        await sendEmail({
          to: event.customerEmail,
          subject: 'Ordine Confermato',
          body: `Il tuo ordine ${event.orderId} è stato confermato`
        });
      }
    }
  });
};

run().catch(console.error);

Testing dei Microservizi

Unit Test:

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
    
    @Mock
    private OrderRepository orderRepository;
    
    @InjectMocks
    private OrderService orderService;
    
    @Test
    void shouldCreateOrder() {
        CreateOrderCommand command = new CreateOrderCommand(
            "CUST123", "PROD456", 2
        );
        
        Order order = orderService.createOrder(command);
        
        verify(orderRepository).save(any(Order.class));
        assertNotNull(order.getId());
    }
}

Integration Test con Testcontainers:

@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {
    
    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Test
    void shouldPersistOrder() {
        Order order = new Order();
        order.setCustomerId("CUST123");
        
        Order saved = orderRepository.save(order);
        
        assertNotNull(saved.getId());
    }
}

Deployment in Produzione – Guida implementazione Microservizi

Containerizzazione con Docker:

# Order Service Dockerfile
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/order-service-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8081
ENTRYPOINT ["java", "-jar", "app.jar"]

Orchestrazione con Kubernetes:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
      - name: order-service
        image: myregistry/order-service:1.0
        ports:
        - containerPort: 8081
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "1Gi"
            cpu: "1000m"
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
  - port: 80
    targetPort: 8081
  type: LoadBalancer

Monitoring con Prometheus e Grafana:

// Aggiungi metriche custom
@Component
public class OrderMetrics {
    
    private final Counter ordersCreated;
    
    public OrderMetrics(MeterRegistry registry) {
        this.ordersCreated = Counter.builder("orders_created_total")
            .description("Numero totale ordini creati")
            .register(registry);
    }
    
    public void incrementOrdersCreated() {
        ordersCreated.increment();
    }
}

CI/CD Pipeline

# .github/workflows/deploy.yml
name: Deploy Microservices

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        
    - name: Build with Maven
      run: mvn clean package
      
    - name: Build Docker image
      run: docker build -t myregistry/order-service:${{ github.sha }} .
      
    - name: Push to registry
      run: docker push myregistry/order-service:${{ github.sha }}
      
    - name: Deploy to Kubernetes
      run: kubectl set image deployment/order-service order-service=myregistry/order-service:${{ github.sha }}

Best Practices per la Produzione – Guida implementazione Microservizi

  1. Service Mesh: Implementa Istio o Linkerd per gestire traffico, security e observability
  2. Circuit Breaker: Usa Resilience4j per evitare cascading failures
  3. Distributed Tracing: Implementa Spring Cloud Sleuth e Zipkin
  4. Centralized Logging: Usa stack ELK (Elasticsearch, Logstash, Kibana)
  5. API Versioning: Versiona le API per backward compatibility
  6. Health Checks: Implementa endpoint /actuator/health

L’Importanza della Formazione del Team

L’implementazione di un’architettura a microservizi richiede competenze specifiche che vanno ben oltre la programmazione tradizionale. La mancanza di formazione adeguata porta a errori architetturali costosi, mancato rispetto delle deadline, codice difficile da mantenere e, inevitabilmente, stress e frustrazione nel team di sviluppo.

I pattern qui descritti – Saga, CQRS, Event Sourcing, BFF – rappresentano soluzioni complesse che richiedono comprensione profonda dei principi distribuiti. Un team non preparato rischia di implementare anti-pattern che negano i benefici dei microservizi, creando quello che viene definito “monolite distribuito”: tutti gli svantaggi dei sistemi distribuiti senza i vantaggi dei microservizi.

Investire nella Formazione con Innovaformazione

Per garantire il successo dei vostri progetti microservices, è fondamentale investire in formazione professionale strutturata. Innovaformazione offre corsi specializzati in microservizi progettati specificamente per team aziendali, con focus pratico su Spring Boot, Spring Cloud, pattern architetturali e best practices di produzione.

I corsi sono disponibili in modalità online classe virtuale con calendario completamente personalizzabile in base alle esigenze del vostro team. Questo permette di formare il team senza interrompere i progetti in corso, mantenendo alta la produttività.

La formazione può inoltre essere finanziata tramite Fondimpresa, con tutto il supporto amministrativo gestito direttamente da Innovaformazione, eliminando la complessità burocratica per la vostra azienda.

Scopri i corsi Microservices QUI.

Investire nella formazione del team significa:

  • Ridurre i tempi di sviluppo attraverso l’adozione di best practices consolidate
  • Minimizzare gli errori architetturali che costano tempo e denaro
  • Aumentare la motivazione del team attraverso competenze moderne e richieste dal mercato
  • Garantire la manutenibilità del codice nel lungo periodo
  • Rispettare le deadline grazie a una pianificazione più accurata

Un team formato è un team che produce software di qualità, rispetta i tempi e lavora con serenità. Non sottovalutate l’importanza di questa componente critica del successo dei vostri progetti microservices.

Conclusione – Guida implementazione Microservizi

L’architettura a microservizi offre scalabilità, resilienza e agilità nello sviluppo, ma richiede una comprensione approfondita dei pattern architetturali e delle best practices. Questa guida ha fornito gli strumenti essenziali per implementare un sistema microservices da zero fino alla produzione, coprendo i pattern fondamentali come Database per Service, API Gateway, BFF, CQRS, Event Sourcing e Saga.

Il successo nell’adozione dei microservizi dipende tanto dalla tecnologia quanto dalle competenze del team. Investite in formazione strutturata e continua per garantire che i vostri progetti raggiungano gli obiettivi prefissati con qualità, tempi rispettati e team motivati.

(fonte) (fonte) (fonte) (fonte)

Per leggere altri articoli tecnici consigliamo di navigare sul nostro blog QUI.

Per informazioni sui nostri corsi rivolti alle aziende, di seguito i contatti:

info@innovaformazione.net – TEL. 3471012275 (Dario Carrassi)

Ti potrebbe interessare

Articoli correlati