Mikrotjänster (Microservices)

Introduktion

Mikrotjänstarkitektur är ett sätt att strukturera en applikation som en samling av små, självständiga tjänster — var och en körandes i sin egen process och kommunicerandes via väldefinierade API:er. Varje tjänst ansvarar för ett specifikt affärsdomän och kan driftsättas, skalas och uppdateras oberoende av de andra.


Monolith vs. Mikrotjänster

Monolitisk arkitektur

I en monolit körs hela applikationen som en enda enhet. All kod — användarhantering, betalning, produktkatalog — är sammanflätad och driftsätts tillsammans.

┌─────────────────────────────────────┐
│           Monolit                   │
│  ┌──────────┐  ┌──────────────┐     │
│  │  Users   │  │   Products   │     │
│  └──────────┘  └──────────────┘     │
│  ┌──────────┐  ┌──────────────┐     │
│  │ Payments │  │   Orders     │     │
│  └──────────┘  └──────────────┘     │
│           En databas                │
└─────────────────────────────────────┘

Problem med monoliter vid skalning:

  • En bugg i betalningsmodulen kan krascha hela applikationen.
  • Hela systemet måste driftsättas om vid minsta ändring.
  • Svårt att skala enskilda delar (t.ex. bara produktsökning).

Mikrotjänstarkitektur

  [Klient]

  [API Gateway]
   /   |   \   \
Users Orders Products Payments
  │      │       │        │
 DB1    DB2     DB3      DB4

Varje tjänst:

  • Har sin egen databas (Database per Service)
  • Exponerar ett REST- eller gRPC-API
  • Kan skalas oberoende
  • Kan skrivas i olika språk

Nyckelkomponenter

API Gateway

Ingångspunkten för alla klientförfrågningar. Den ansvarar för:

  • Routing till rätt tjänst
  • Autentisering och auktorisation
  • Rate limiting
  • Load balancing
Klient → API Gateway → /users    → User Service
                     → /orders   → Order Service
                     → /products → Product Service

Service Discovery

Tjänsterna måste kunna hitta varandra dynamiskt — IP-adresser och portar ändras när containrar startar och stoppar.

Verktyg: Consul, Kubernetes DNS, AWS Cloud Map

Message Broker

Asynkron kommunikation via events istället för direkta HTTP-anrop ökar robustheten.

Order Service ──publishes──▶ "order.created" ──▶ Message Broker

                                        ┌───────────────┤
                                        ▼               ▼
                               Payment Service   Email Service

Verktyg: Apache Kafka, RabbitMQ, AWS SQS


Exempel — Order Service (Java / Spring Boot)

// Order Service — en självständig mikrotjänst

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    private final OrderService    orderService;
    private final KafkaProducer   eventBus;

    public OrderController(OrderService orderService, KafkaProducer eventBus) {
        this.orderService = orderService;
        this.eventBus     = eventBus;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderResponse createOrder(@RequestBody @Valid CreateOrderRequest request) {
        // 1. Skapa ordern i vår egen databas
        Order order = orderService.create(request);

        // 2. Publicera event — Payment Service lyssnar och hanterar betalningen
        eventBus.publish("order.created", new OrderCreatedEvent(
            order.getId(),
            order.getCustomerId(),
            order.getTotalAmount()
        ));

        return new OrderResponse(order.getId(), order.getStatus());
    }

    @GetMapping("/{id}")
    public OrderResponse getOrder(@PathVariable Long id) {
        return orderService.findById(id)
            .map(OrderResponse::from)
            .orElseThrow(() -> new OrderNotFoundException(id));
    }
}

// Payment Service lyssnar på events från Order Service
@Component
public class OrderEventListener {

    private final PaymentService paymentService;

    @KafkaListener(topics = "order.created")
    public void onOrderCreated(OrderCreatedEvent event) {
        // Hanterar betalning oberoende av Order Service
        paymentService.processPayment(
            event.getOrderId(),
            event.getCustomerId(),
            event.getAmount()
        );
    }
}

Designmönster för Mikrotjänster

MönsterProblem det löser
Circuit BreakerFörhindrar kaskadfel när en tjänst är nere
SagaHanterar distribuerade transaktioner
API GatewayGer en enda ingångspunkt för klienter
Database per ServiceLöst koppling — varje tjänst äger sin data
Event SourcingAuditlog och tidresning via events

Circuit Breaker — exempel

// Om Payment Service är nere, bryt kretsen istället för att hänga
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackPayment")
public PaymentResult processPayment(PaymentRequest request) {
    return paymentClient.charge(request);
}

public PaymentResult fallbackPayment(PaymentRequest request, Exception ex) {
    // Köa betalningen för senare försök
    paymentQueue.enqueue(request);
    return PaymentResult.queued("Betalningen köas och behandlas snart.");
}

Fördelar

  • Oberoende driftsättning — Uppdatera en tjänst utan att röra de andra.
  • Teknikfrihet — Välj bästa verktyget för varje tjänst.
  • Skalbarhet — Skala bara de tjänster som är under hög last.
  • Felatolerabilitet — En kraschad tjänst påverkar inte hela systemet.

Nackdelar

  • Komplexitet — Distribuerade system är genuint svårare att debugga.
  • Nätverksfördröjning — Tjänstkommunikation är långsammare än inprocess-anrop.
  • Datakonsistens — Transaktioner som spänner flera tjänster är svåra.
  • Operationell overhead — Kräver container-orchestrering (Kubernetes), monitoring, tracing.

Tumregel: Börja med en välstrukturerad monolit. Bryt ut mikrotjänster när du har tydliga domängränser och verklig skalningsproblematik.


Av Victor Hernandez från Bytebase.se