Projektowanie skalowalnych mikroserwisów w Javie z użyciem Spring Boot i Apache Kafka

0
16
Rate this post

Z tego artykułu dowiesz się…

Po co w ogóle mikroserwisy z Kafka przy ograniczonym budżecie

Kiedy mikroserwisy faktycznie pomagają, a kiedy szkodzą

Mikroserwisy w Javie ze Spring Boot i Apache Kafka mają sens dopiero wtedy, gdy realnie rozwiązują problem skali, zwinności lub niezawodności. Dla prostego systemu z kilkoma ekranami, jednym zespołem i niewielkim ruchem monolit jest zwykle tańszy, szybszy w budowie i łatwiejszy w utrzymaniu. Cięcie wszystkiego na 20 usług „bo tak robią duzi” kończy się serią integracyjnych dramatów, a nie oszczędnością.

Architektura mikroserwisów w Javie zaczyna się opłacać, gdy:

  • zespół jest wielozespołowy lub będzie rósł i potrzebuje niezależnie rozwijać funkcje (np. płatności, katalog, obsługa klienta),
  • różne moduły systemu mają bardzo różne profile obciążenia – np. odczyty katalogu idą w tysiącach RPS, a panel administracyjny ma marginalny ruch,
  • system musi być wysoko dostępny – awaria jednego modułu nie może „położyć” reszty,
  • pojawi się potrzeba niezależnego skalowania komponentów oraz strategii „polyglot” (nie wszystko w jednej technologii i bazie).

W praktyce wiele zespołów przyznaje, że „przeskalowało się architekturą”: startowali od kilkunastu mikroserwisów, a dopiero po roku zrozumieli granice domenowe i zaczęli część z nich łączyć. Do tego momentu ponosili koszty DevOps, złożonych wdrożeń, wersjonowania API i problemów z debugowaniem, które nie miały pokrycia w faktycznej wartości biznesowej.

Dlaczego Kafka bywa lepsza od REST przy dużym ruchu

REST jest prosty, intuicyjny i świetnie sprawdza się na starcie. Przy rosnącej liczbie usług i coraz większym ruchu pojawiają się jednak problemy: lawina połączeń synchronicznych, n-calls problem (jedna operacja biznesowa wywołuje kaskadę requestów), częstsze time-outy i trudne do ogarnięcia zależności. Apache Kafka i podejście event-driven przenoszą komunikację na strumienie zdarzeń, rozpinając interakcje w czasie.

Wysokie obciążenie i rozproszenie lepiej znosi architektura zdarzeniowa, bo:

  • producenci zdarzeń nie muszą czekać na odpowiedź konsumentów – wystarczy, że zapiszą event do topica,
  • dodanie nowego serwisu przetwarzającego określone zdarzenia nie wymaga zmian u nadawców (subskrypcja topica),
  • Kafka przechowuje zdarzenia przez określony czas – można je ponownie przetworzyć, odtworzyć stan, zreprocesować błędy,
  • skalowanie poziome usług to często po prostu zwiększenie liczby konsumentów w consumer group i liczby partycji.

Nie oznacza to, że REST przestaje być potrzebny. Interfejsy zewnętrzne, panele administracyjne czy proste operacje CRUD nadal korzystają z HTTP. Komunikacja asynchroniczna event driven ma sens tam, gdzie rośnie skala, potrzebna jest odporność na awarie i luzowanie zależności czasowych między usługami.

Koszt wejścia: umiejętności, narzędzia i pułapki startowe

Mikroserwisy ze Spring Boot i Apache Kafka wymagają większego „progu wejścia” niż klasyczny monolit. Trzeba doliczyć:

  • kompetencje: Java/Spring, podstawy architektury zdarzeniowej, administrowanie Kafką lub przynajmniej świadomość jej zachowania,
  • narzędzia: Git, CI/CD, monitoring (Prometheus, Grafana, ELK/EFK), centralna konfiguracja lub przynajmniej sensowne profile środowiskowe,
  • kulturę pracy: wersjonowanie kontraktów, testy integracyjne, zarządzanie schema zdarzeń, logika „design first” zamiast „koduj i poprawimy potem”.

Na starcie da się ciąć koszty: zamiast rozbudowanego Spring Cloud Config można użyć zwykłych plików YAML/Properties zasilanych zmiennymi środowiskowymi, zamiast komercyjnych platform monitoringu – open-source. Kluczem jest świadome podejście: zaczynanie od minimalnego zestawu, który chroni przed najdroższymi wpadkami (np. brak logów, brak metryk, brak sensownego sposobu na roll-back wersji).

Historia małej firmy, która za wcześnie przeszła na mikroserwisy

Częsty scenariusz: mały software house buduje system rezerwacji. Zamiast monolitu powstaje 10 serwisów: użytkownicy, rezerwacje, płatności, faktury, powiadomienia, raporty itd. Początkowo wszystko na lokalnym Dockerze, bez zaawansowanej automatyzacji. Po kilku miesiącach:

  • czas wdrożeń wydłużył się, bo każda zmiana dotykała 3–4 serwisów i wymagała ich koordynowanego deploymentu,
  • devowie spędzali więcej czasu na „poszukiwaniu requestu” po logach i trace’ach niż na implementacji funkcji,
  • rachunek za chmurę rósł przez kilkanaście małych instancji, z których większość większość doby się nudziła,
  • nagle okazało się, że Kafka została użyta wszędzie, nawet tam, gdzie proste REST + DB w zupełności wystarczało, a zespół nie ogarniał tematu retencji i partycji.

Fundamenty architektury mikroserwisów w Javie – co ustalić zanim powstanie pierwszy serwis

Wyznaczanie granic serwisów na bazie domeny

Najczęstszy błąd: dzielenie systemu według warstw technicznych („serwis do obsługi użytkowników”, „serwis do wysyłania e-maili”), a nie według domeny biznesowej. Lepszym punktem startu jest podejście bounded context z DDD. Serwis odpowiada za konkretny obszar znaczeniowy: „Zamówienia”, „Płatności”, „Katalog produktów”, „Obsługa reklamacji”.

Praktyczny sposób: opisanie kilku kluczowych scenariuszy biznesowych w formie „user journey” i zaznaczenie, gdzie zmienia się język pojęć. Tam zwykle zmienia się kontekst i tam warto stawiać granice serwisów. Dla małego zespołu wystarczą proste diagramy i spis ról – bez wielkich workshopów.

Na start lepiej mieć mniej, ale mądrzejszych serwisów. Przebudowa kilku endpointów w jednym serwisie jest tańsza niż utrzymywanie zbyt wielu małych usług z osobnymi bazami, migracjami i konfiguracją.

Strategia komunikacji: synchronicznie czy asynchronicznie

Na etapie projektu trzeba zadać dwa pytania: co musi być natychmiast i co tak naprawdę jest tylko informacją o fakcie, który można przetworzyć później.

REST/gRPC dobrze się sprawdza, gdy:

  • użytkownik czeka na natychmiastową odpowiedź (logowanie, sprawdzenie salda),
  • potrzebny jest szybki odczyt danych w niewielkim zakresie,
  • współpracują ze sobą 2–3 serwisy w prostym łańcuchu.

Kafka i inne mechanizmy kolejkowania wygrywają przy:

  • wielu odbiorcach tego samego zdarzenia (płatność zatwierdzona i reagują raporty, księgowość, powiadomienia),
  • wysokim wolumenie żądań, który trzeba buforować,
  • operacjach „niepilnych z punktu widzenia użytkownika” (generowanie raportów, synchronizacja danych, integracje z zewnętrznymi systemami).

Dobrą praktyką jest zachowanie hybrydy: rdzeń interakcji użytkownika (HTTP) plus strumienie zdarzeń do komunikacji między serwisami. Próba zrobienia wszystkiego synchronicznie lub całkowicie asynchronicznie rzadko kończy się czymś tanim i prostym.

Decyzje na start: nazewnictwo, wersjonowanie API i kontrakty

Nawet przy ograniczonym budżecie kilka decyzji na początku oszczędza tygodnie późniejszych uzgodnień:

  • nazewnictwo – spójne nazwy serwisów, endpointów, nazw topiców Kafki (np. orders-service, payments-service, topici typu orders.created, payments.completed),
  • wersjonowanie API – choćby prosty schemat /api/v1/.... Zmiany niekompatybilne wrzucamy do v2, a stare kontrakty wygaszamy w kontrolowany sposób,
  • kontrakty między zespołami – nawet w jednym zespole kontrakty HTTP (OpenAPI/Swagger) i schemy zdarzeń (Avro/JSON Schema) pomagają utrzymać porządek i testy kontraktowe.

Minimalny proces: każdy breaking change w API/evencie musi mieć akceptację odbiorców i plan wygaszenia starej wersji. Przy małym zespole to może być jeden plik z listą kontraktów i terminami, ale musi istnieć.

Polityka danych – źródło prawdy vs kopie

W architekturze mikroserwisów w Javie każdy serwis jest odpowiedzialny za swój fragment danych. Kluczowe pytania:

  • który serwis jest źródłem prawdy dla danej encji (np. zamówienie, klient, faktura),
  • które dane mogą być kopiami (cache, read model w innym serwisie),
  • jak aktualizowane są kopie: synchronicznie (REST) czy asynchronicznie (Kafka, eventy „entity changed”).

Typowy wzorzec: serwis właściciel (z własną bazą) publikuje zdarzenia o zmianach, a inne serwisy utrzymują swoje lokalne read modele. Wymaga to świadomego godzenia się na spójność ostateczną, ale mocno odciąża centralne bazy i usuwa potrzebę cross-schema joinów.

Spring Boot jako „konik roboczy” – prosty szkielet mikroserwisu

Minimalna konfiguracja projektu pod mikroserwis

Dla pojedynczego mikroserwisu w Spring Boot wystarczy kilka zależności:

  • spring-boot-starter-web – REST (o ile serwis wystawia API),
  • spring-boot-starter-actuator – metryki, health checki,
  • driver do bazy (np. postgresql) i spring-boot-starter-data-jpa lub alternatywa (jOOQ, MyBatis), jeśli JPA jest za ciężkie,
  • spring-kafka lub spring-cloud-stream – integracja z Apache Kafka.

Konfiguracja środowisk: application.yml (lub .properties) z osobnymi profilami: dev, test, prod. Zamiast od razu budować konfigurację centralną (Config Server, Vault, Consul), przy małej skali spokojnie wystarczy:

  • wspólny plik z wartościami domyślnymi (np. application-common.yml),
  • nadpisanie kluczowych parametrów przez zmienne środowiskowe (hasła, URL-e, konfiguracja Kafki).

Wariant „budżetowy”: jeden szablon projektu (np. w postaci archetypu Maven lub wzorcowego repo) z minimalną konfiguracją, który klonuje się przy tworzeniu kolejnych mikroserwisów. Zespół nie przepala czasu na wymyślanie struktury od zera za każdym razem.

Jak nie przekombinować projektu

Naturalna pokusa: zbudować wielomodułowy projekt z osobnymi modułami api, service, domain, persistence, a do tego dołożyć kilkanaście starterów Spring Cloud. Dla małego zespołu to przepis na powolne buildy, skomplikowany pipeline i frustrację nowych programistów.

Bezpieczniejsza opcja na start:

  • jeden moduł Maven/Gradle na serwis,
  • prosty podział pakietów: controller, service, repository, config, events,
  • osobne repozytorium na każdy serwis lub monorepo z bardzo jasnym podziałem katalogów (w małych firmach monorepo bywa prostsze operacyjnie).

Dopiero gdy serwis rzeczywiście urośnie (setki klas, różne modele deployu) warto myśleć o wydzielaniu modułów. Zwiększanie złożoności przed czasem jest kosztowne – zarówno w roboczogodzinach, jak i w onboarding nowych osób.

Wzorce w Spring Boot, które przyspieszają rozwój

Spring Boot ma kilka gotowych mechanizmów, które istotnie skracają czas developmentu:

  • Actuator – zdrowie aplikacji pod /actuator/health, metryki pod /actuator/metrics, informacje o buildzie. W połączeniu z Prometheusem i Grafaną daje szybki wgląd w stan produkcji,
  • Bean Validation (@Valid, @NotNull, @Size) – walidacja danych wejściowych na brzegu aplikacji, mniej kodu „ifów” i spójniejsze komunikaty błędów,
  • Spring Data – standardowe operacje na bazie danych za pomocą kilku interfejsów, bez żmudnego pisania DAO; przy bardziej złożonych zapytaniach można dołożyć własne metody lub przejść na jOOQ tam, gdzie ma to sens,
  • konfiguracja przez adnotacje – prosty podział odpowiedzialności dzięki @Configuration, @ConfigurationProperties, profilom środowiskowym i automatycznej injekcji zależności, zamiast skryptów XML i ręcznego zarządzania beanami,
  • obsługa błędów globalnie@ControllerAdvice i własne klasy ExceptionHandler pozwalają ustandaryzować odpowiedzi błędów bez duplikowania kodu w każdym kontrolerze.

W małych zespołach dobrym ruchem jest stworzenie jednego „wzorcowego” serwisu, z którego bierze się gotowe konfiguracje: bezpieczeństwa, logowania, obsługi błędów, health checków, integracji z Kafka. Kopiowanie sprawdzonych rozwiązań bywa tańsze niż ambitne budowanie wspólnej biblioteki, którą trzeba później wersjonować i utrzymywać.

Na poziomie kodu lepiej inwestować w kilka powtarzalnych schematów niż w rozbudowane frameworki własnej produkcji. Przykładowo: jeden standardowy filtr logujący request/response, jedna klasa odpowiedzi błędu dla całej platformy, jeden sposób mapowania DTO na encje. Zespół szybciej wdraża nowych ludzi i nie traci godzin na analizę „jak to jest zrobione akurat w tym serwisie”.

Dobrym uzupełnieniem będzie też materiał: Architektura event-streamingowa w ekosystemie Spring — warto go przejrzeć w kontekście powyższych wskazówek.

Nawet przy ograniczonym budżecie da się zbudować rozsądną architekturę: kilka dobrze nazwanych mikroserwisów w Spring Boot, jasne kontrakty HTTP i zdarzeń, prosta integracja z Apache Kafka oraz świadome decyzje, co jest krytyczne i synchroniczne, a co może poczekać w kolejce. Połączenie tych elementów daje platformę, którą można skalować stopniowo, zamiast przepalać środki na przedwczesną „enterprise’owość”.

Kobieta zapisuje na białej tablicy hasło Use APIs podczas planowania software
Źródło: Pexels | Autor: ThisIsEngineering

Apache Kafka w praktyce – od jednego topica do rozsądnej platformy eventowej

Jak zacząć tanio: jedna instancja, kilka topiców

Początek przy ograniczonym budżecie nie wymaga klastra na kilkanaście brokerów. Dla małej lub średniej platformy wystarczy:

  • 1–3 brokerów Kafki (lokalnie lub w chmurze),
  • oddzielne topici na kluczowe zdarzenia domenowe,
  • prosty naming: <kontekst>.<akcja>, np. orders.created, orders.canceled, payments.completed.

Na starcie lepiej mieć kilka dobrze nazwanych topiców niż „supertopic” events z mieszanką wszystkiego. Prosta zasada: jeśli różne serwisy mają inny lifecycle zdarzeń albo inne wymagania retencji, dostają osobne topici.

Konfiguracja Spring Boot + Kafka – wariant minimalistyczny

Najprostsze podejście to użycie spring-kafka z kilkoma beanami konfiguracyjnymi. Przykład producenta:

@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory(KafkaProperties properties) {
        Map<String, Object> config = new HashMap<>(properties.buildProducerProperties());
        config.put(ProducerConfig.ACKS_CONFIG, "all");
        config.put(ProducerConfig.RETRIES_CONFIG, 3);
        return new DefaultKafkaProducerFactory<>(config);
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate(ProducerFactory<String, String> producerFactory) {
        return new KafkaTemplate<>(producerFactory);
    }
}

Podobnie dla konsumenta:

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

    @Bean
    public ConsumerFactory<String, String> consumerFactory(KafkaProperties properties) {
        Map<String, Object> config = new HashMap<>(properties.buildConsumerProperties());
        config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        return new DefaultKafkaConsumerFactory<>(config);
    }

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
            ConsumerFactory<String, String> consumerFactory) {
        ConcurrentKafkaListenerContainerFactory<String, String> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory);
        factory.setConcurrency(3);
        return factory;
    }
}

Dzięki temu serwis może reagować na zdarzenia zwykłą metodą:

@Service
public class OrderCreatedListener {

    @KafkaListener(topics = "orders.created", groupId = "billing-service")
    public void handle(String eventPayload) {
        // deserializacja JSON / Avro, logika domenowa
    }
}

Wariant „budżetowy” na start: serializacja JSON z prostym schematem, logikę schem i ew. migracji dorzucić dopiero, gdy liczba eventów i usług zaczyna rosnąć. JSON jest wolniejszy i cięższy niż Avro, ale na małej skali liczy się szybkość wdrożenia, nie oszczędność 5% CPU.

Topic, partycja, grupa konsumencka – jak to przełożyć na mikroserwisy

Kafka ma trzy proste klocki, z których można zbudować elastyczną platformę eventową:

  • topic – kanał zdarzeń z jednego obszaru,
  • partycja – jednostka równoległego przetwarzania; porządek jest gwarantowany tylko w ramach partycji,
  • groupId – konsumenty w tej samej grupie dzielą się partiami topicu.

Przekładając to na praktykę:

  • każdy mikroserwis ma własne groupId, np. billing-service, reporting-service, żeby każdy mógł dostać swoje kopie zdarzeń,
  • liczbę partycji zwiększa się wtedy, gdy pojawia się realna potrzeba większej równoległości (czas przetwarzania, SLA), a nie „na wszelki wypadek”,
  • klucz partycjonowania (np. orderId) dobiera się tak, aby logika wymagała zachowania kolejności tylko w obrębie jednego klucza.

Typowy scenariusz: orders.created ma 6 partycji, kluczem jest orderId, a serwis billing-service ma concurrency ustawione na 6–8. Gdy ruch rośnie, można dołożyć repliki serwisu albo podnieść concurrency bez grzebania w kodzie.

Retencja, kompaktowanie i „śmieci” w Kafce

Koszt Kafki rośnie głównie z ilością danych trzymanych w logach. Kilka prostych zasad trzyma rachunki w ryzach:

  • dla topiców typu „zdarzenia jednorazowe” (np. order.created) wystarczy retencja kilka dni,
  • dane, które mają reprezentować stan (np. customer.updated), można trzymać w topicu kompaktowanym (cleanup.policy=compact),
  • przed włączeniem długiej retencji upewnić się, że faktycznie ktoś ma use-case na odczyt „starej historii” z Kafki, a nie z gotowego data lake/reportingu.

Nadmierne zbieranie wszystkiego „bo może się przyda” szybko kończy się potrzebą większych dysków i silniejszych brokerów. Tańsze jest dogranie kilku dedykowanych tematów z rozsądną retencją niż przetrzymywanie rocznych logów w jednym, gigantycznym topicu.

Testowanie integracji z Kafką bez rozbijania budżetu

Do testów lokalnych wystarczy w większości przypadków Testcontainers z uruchamianą w locie Kafką. Nie trzeba stawiać osobnego klastra testowego tylko do unitów i integracji. Przykładowy fragment konfiguracji testu:

@Testcontainers
@SpringBootTest
class OrderEventPublisherTest {

    @Container
    static KafkaContainer kafka = new KafkaContainer("confluentinc/cp-kafka:7.0.0");

    @DynamicPropertySource
    static void registerKafka(DynamicPropertyRegistry registry) {
        registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
    }

    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    // testy publikacji / konsumpcji
}

Podejście z kontenerem Kafki w testach integracyjnych urealnia zachowanie produkcyjne i zmniejsza ryzyko „niespodzianek” po deployu, bez konieczności opłacania stałego środowiska testowego o pełnej skali.

Projektowanie komunikacji asynchronicznej – kontrakty zdarzeń i spójność danych

Co jest eventem, a co nim nie jest

Nie każde wywołanie HTTP z definicji powinno stać się zdarzeniem w Kafce. Dobrze jest odróżnić:

  • polecenia (commands) – intencje wykonywane zwykle synchronicznie: „utwórz zamówienie”, „zaktualizuj adres klienta”,
  • zdarzenia domenowe – fakty, które już się wydarzyły: „zamówienie utworzone”, „płatność odrzucona”.

Polecenia mogą być przesyłane HTTP/gRPC, natomiast zdarzenia są naturalnym kandydatem do Kafki. Mniejsza liczba typów eventów, ale dobrze zdefiniowanych, ułatwia utrzymanie całej platformy w rozsądnej formie.

Kontrakty zdarzeń – prosty, ale sztywny szkielet

Dla każdego typu zdarzenia przydaje się krótka, jednoznaczna specyfikacja. Najprostszy wariant to JSON ze wspólnym nagłówkiem:

{
  "eventId": "uuid",
  "eventType": "order.created",
  "occurredAt": "2024-03-01T10:15:30Z",
  "source": "orders-service",
  "payload": {
    "orderId": "123",
    "customerId": "456",
    "totalAmount": 100.50,
    "currency": "PLN",
    "status": "CREATED"
  }
}

Jeśli zespół jest niewielki, zamiast od razu wdrażać Avro i Schema Registry można zacząć od:

  • jednego repozytorium z JSON Schema dla typów zdarzeń,
  • prostej walidacji schem po stronie producenta i konsumenta (np. biblioteka JSON Schema w testach),
  • jasnych zasad zmian: dopuszczalne tylko ewolucyjne (dodanie pola opcjonalnego, nieusuwanie istniejących).

Kolejny krok – dopiero gdy liczba serwisów rośnie – to przejście na Avro/Protobuf + Schema Registry, najlepiej z gotowej usługi w chmurze, zamiast utrzymywać całość samodzielnie.

Wersjonowanie zdarzeń bez paraliżu platformy

Zmiany w strukturze eventów bywają bolesne, jeśli robi się je ad hoc. Prosty model wersjonowania:

  • typ zdarzenia identyfikowany nazwą (order.created), bez numeru wersji w nazwie topicu,
  • schemat jest ewolucyjny: dopuszcza nowe, opcjonalne pola; konsument może je ignorować,
  • w przypadku prawdziwego breaking change – nowy topic (orders.created.v2) z okresem przejściowym.

Mechanizm podobny do REST: dopóki da się utrzymać wsteczną kompatybilność, robi się to w jednym kanale. Przeskok na v2 jest wyjątkiem, który wymaga uzasadnienia (np. całkowita zmiana modelu płatności).

Dopiero po pierwszym większym incydencie (błędne eventy płatności, trudne do „odkręcenia”) zespół wrócił do rysowania domeny i scalił kilka usług. Widoczny zysk? Mniej kłopotów z deployem, prostsza komunikacja w zespole i niższe rachunki za infrastrukturę. Dobrze zaprojektowany monolit (lub 2–3 większe serwisy) często jest bardziej opłacalnym etapem przejściowym niż od razu „pełne mikroserwisy z Kafką”. Przy planowaniu architektury, praktyczne wskazówki: programowanie potrafią oszczędzić tygodnie eksperymentów i nieudanych podejść.

Spójność ostateczna – jak z nią żyć w praktyce

Przy komunikacji asynchronicznej trzeba zaakceptować, że część serwisów przez chwilę „nie wie” o zmianach w innych. Zamiast próbować to ukryć, lepiej zaprojektować system wprost z tą właściwością:

  • API zwraca stan lokalny, a nie „prawdy absolutne” ze wszystkich modułów,
  • interfejs użytkownika pokazuje, że dane mogą się synchronizować (np. status „w trakcie aktualizacji”),
  • operacje krytyczne (np. pobranie salda przed wypłatą) korzystają z właściciela danych, nie z kopii.

Przykład z życia: serwis zamówień zapisuje nowy adres dostawy klienta i wysyła event. Serwis wysyłek aktualizuje swój model w ciągu sekund. W tym krótkim oknie UI może pokazywać status „aktualizujemy dane wysyłkowe”, a przy generowaniu etykiety kurierskiej wykorzystywany jest już zaktualizowany model wysyłek, a nie jeszcze starszy cache.

Outbox pattern – bezpieczne publikowanie zdarzeń

Największy klasyk problemów: zapis do bazy się udał, publikacja eventu do Kafki już nie. Albo odwrotnie. Rozwiązaniem, które dobrze gra z relacyjnymi bazami i Spring Boot, jest outbox pattern:

  • w transakcji biznesowej zapisujemy do swojej tabeli (np. orders) oraz do tabeli outbox_events,
  • osobny proces (batch, scheduler, Debezium) odczytuje rekordy z outboxa i publikuje je do Kafki,
  • po potwierdzonej publikacji zaznacza event jako przetworzony (soft delete, flaga).

Przykładowa encja outboxu:

@Entity
@Table(name = "outbox_events")
public class OutboxEvent {

    @Id
    private String id;

    private String aggregateType; // np. ORDER
    private String aggregateId;   // np. orderId

    private String eventType;     // np. order.created

    @Lob
    private String payload;

    private Instant createdAt;

    private boolean published = false;
}

Spring Boot ułatwia sprawę, bo wystarczy prosty @Scheduled job lub listener CDC, który co kilka sekund publikuje nieprzetworzone rekordy. Koszt wdrożenia jest niski, a w zamian znika ryzyko utraty zdarzeń przy chwilowych problemach z brokerem.

Idempotencja – jak uniknąć skutków ponownej dostawy

Kafka, w zależności od konfiguracji i błędów po stronie konsumenta, może dostarczyć zdarzenie więcej niż raz. Zamiast liczyć na „once and exactly once”, prościej i taniej jest zaprojektować konsumentów idempotentnych. Kilka praktyk:

  • każde zdarzenie ma unikalne eventId,
  • serwis utrzymuje tabelę/log przetworzonych eventów (np. processed_events(event_id)),
  • przed wykonaniem akcji sprawdza, czy eventId już nie występuje.

W wielu przypadkach wystarczy, że kluczowym identyfikatorem jest np. orderId i logika jest napisana „nadpisująco” (ostatni stan wygrywa). To tańsze niż utrzymywanie osobnej tabeli eventów, o ile biznesowo nie trzeba mieć ścisłego audytu w każdym serwisie.

Obsługa błędów i DLQ bez rozbudowanej orkiestracji

Nie każdy event da się przetworzyć od razu – czasem z powodu błędów danych, czasem z powodu awarii serwisu zależnego. Sensowne minimum:

  • prosty retry na poziomie konsumenta (kilka prób z opóźnieniem),
  • topic DLQ (Dead Letter Queue) na eventy, które po kilku próbach nadal się wywalają,
  • dashboard z monitoringiem DLQ (np. Grafana + licznik metryk z Actuatora).

Konfiguracja Spring Kafka pozwala zbudować to niewielkim kosztem, np. przez SeekToCurrentErrorHandler i DeadLetterPublishingRecoverer. Bardziej wyszukane strategie (saga orchestration, dedykowane orkiestratory) zachowują sens dopiero przy dużej liczbie usług i skomplikowanych przepływach.

Przy DLQ przydaje się prosta procedura operacyjna: kto ogląda ten topic, jak często i co robi z rekordami. Najprościej – mały panel administracyjny lub skrypt, który pozwala operatorowi podejrzeć treść eventu, oznaczyć go jako „do odrzucenia” albo „do ponownego przetworzenia” i odesłać na właściwy topic. To dalej jest tanie, ręczne rozwiązanie, ale ratuje produkcję przy braku rozbudowanego zespołu SRE.

Przy bardziej wrażliwych przepływach lepszym kompromisem jest rozróżnienie błędów technicznych i biznesowych. Te pierwsze zwykle nadają się do retry (np. chwilowa niedostępność zewnętrznego API). Te drugie (np. nieistniejący klient, usunięty produkt) często od razu lądują w DLQ z wyraźnym kodem błędu i kontekstem. Dzięki temu nie mieli się bez sensu tych samych eventów, a zespół biznesowy może świadomie podjąć decyzję: poprawiamy dane, czy świadomie ignorujemy przypadek.

Dobrą praktyką jest też trzymanie liczby DLQ w ryzach. Jeżeli licznik metryk zaczyna rosnąć gwałtownie, alert powinien obudzić nie tylko osobę „od infrastruktury”, ale też właściciela domeny. Mikroserwisy z Kafka łatwo zamienić w czarną skrzynkę, gdzie „coś tam lata w eventach”; jasne zasady obsługi DLQ przywracają nad tym kontrolę, bez inwestowania w drogie platformy orkiestracyjne.

Przy ograniczonym budżecie sensowne jest, by te wszystkie mechanizmy – idempotencja, outbox, DLQ – były zamknięte w prostym, wspólnym module lub starterze Spring Boot. Zespół implementuje wtedy raz schemat zdarzeń, logger przetworzonych eventów, konfigurację retry i publikowania do DLQ, a kolejne serwisy tylko to rozsądnie parametryzują. Zmniejsza to koszt wdrażania nowych komponentów i eliminuje całą klasę „kreatywnych” rozwiązań, które później trudno utrzymać.

Cała architektura mikroserwisów z Kafka i Spring Boot daje największy zwrot wtedy, gdy jest rozwijana pragmatycznie: mały, stabilny szkielet, świadome ograniczanie złożoności i systematyczne dbanie o koszty – zarówno chmurowe, jak i te czysto zespołowe. Dzięki temu nawet niewielki zespół jest w stanie zbudować platformę, która skaluje się wtedy, kiedy faktycznie rośnie biznes, a nie tylko faktura za infrastrukturę.

Skalowanie konsumentów i partycji – praktyka zamiast teorii

Kafka kusi możliwością „nieskończonej” skalowalności, ale budżet zwykle sprowadza tę deklarację na ziemię. Klucz to rozsądne podejście do liczby partycji i sposobu skalowania konsumentów.

Przemyślane klucze i liczba partycji

Im więcej partycji, tym większa możliwość równoległego przetwarzania. Jednocześnie rośnie koszt utrzymania klastra, ilość open file handles, czas rebalancingu i narzut operacyjny. Dobry punkt startowy przy niewielkiej liczbie serwisów:

  • dla „gorących” topiców (zamówienia, płatności) – 6–12 partycji,
  • dla „zimniejszych” (logi domenowe, notyfikacje e-mail) – 1–3 partycje,
  • dla topiców DLQ – zwykle 1 partycja wystarczy, chyba że generuje się ich bardzo dużo.

Ważniejszy niż sama liczba partycji bywa dobór klucza wiadomości. Kilka prostych reguł:

  • dla procesów wymagających porządku per agregat (np. orderId) używaj właśnie tego identyfikatora jako klucza,
  • unikaj „stałego” klucza (np. "all") – wszystko trafi do jednej partycji i zjada sens skalowania,
  • jeśli nie ma wymogu zachowania kolejności – można użyć np. UUID lub innego rozproszonego klucza.

Dobór partycji i kluczy warto testować na małym środowisku wydajnościowym (choćby docker-compose + prosty generator eventów), zanim wjedzie się z tym w produkcję. Zmiana liczby partycji później jest możliwa, ale komplikuje bilansowanie obciążenia i może wpływać na semantykę kolejności.

Skalowanie konsumentów z użyciem Spring Boot

Spring Kafka daje prosty sposób na skalowanie: zwiększenie liczby instancji serwisu lub wątków konsumentów. Zwykle bardziej przewidywalne jest skalowanie horyzontalne (więcej instancji) niż dokładanie wątków wewnątrz jednej JVM.

Przykładowa konfiguracja konsumenta z konkurencyjnymi listenerami:

@Configuration
@EnableKafka
public class KafkaConsumerConfig {

    @Bean
    public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory(
            ConsumerFactory<String, String> consumerFactory) {

        ConcurrentKafkaListenerContainerFactory<String, String> factory =
                new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory);
        factory.setConcurrency(3); // liczba wątków per instancja
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);
        return factory;
    }
}

Przy ograniczonych zasobach lepiej zacząć od jednego wątku na partycję (sumarycznie, z uwzględnieniem wszystkich instancji serwisu). Kiedy pojawiają się opóźnienia, łatwiej jest dołożyć kolejną instancję niż od razu przebudowywać architekturę.

Batch processing zamiast turbo-skalowania

Jeśli logika jest czasożerna, a nie CPU-intensywna, bardzo opłacalnym podejściem bywa przetwarzanie w paczkach. Zamiast przetwarzać każdy event osobno, konsument może zbierać je w niewielkie batch-e.

Podstawowa konfiguracja batch konsumera w Spring Kafka:

@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> batchKafkaListenerContainerFactory(
        ConsumerFactory<String, String> consumerFactory) {

    ConcurrentKafkaListenerContainerFactory<String, String> factory =
            new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory);
    factory.setBatchListener(true); // włącza odbiór batch-y
    factory.setConcurrency(2);
    return factory;
}
@KafkaListener(
        topics = "orders.created",
        containerFactory = "batchKafkaListenerContainerFactory")
public void handleOrdersBatch(List<ConsumerRecord<String, String>> records) {
    // przetwarzanie w paczce: walidacja, zapis hurtowy do DB, itp.
}

Batch-e są szczególnie opłacalne przy intensywnej komunikacji z bazą danych: zamiast 1000 pojedynczych insertów można wykonać kilka dużych operacji, znacząco redukując narzut. W wielu środowiskach przynosi to większy zwrot niż agresywne skalowanie liczby instancji.

Dłoń wskazująca schemat blokowy na białej tablicy
Źródło: Pexels | Autor: RDNE Stock project

Monitorowanie i observability na „budżetowym” poziomie

Bez sensownego podglądu trudno ocenić, czy architektura naprawdę skaluje się i ile kosztuje. Nie trzeba od razu kompletnej platformy observability – wystarczy kilka prostych klocków.

Podstawowe metryki, które faktycznie pomagają

Zamiast setek wskaźników, lepszy zestaw „na start” obejmuje:

  • lag konsumenta – ile rekordów czeka w topicu na przetworzenie,
  • czas przetwarzania eventu – średni i percentyle (P95, P99) per serwis,
  • liczba błędów – ile eventów trafia do DLQ per topic,
  • wolumen danych – liczba wiadomości i rozmiar (MB) na topic na godzinę/dzień.

Spring Boot Actuator daje sporą część metryk „z pudełka” i łatwo go połączyć z Prometheusem + Grafaną. Przy małym zespole wystarczy jeden dashboard z kilkoma panelami, zamiast rozbudowanego kokpitu.

Zdrowie mikroserwisu w kontekście Kafki

Klasyczny endpoint /actuator/health można rozszerzyć o komponent, który weryfikuje status połączenia z brokerem. Przykładowy HealthIndicator:

@Component
public class KafkaHealthIndicator implements HealthIndicator {

    private final KafkaTemplate<String, String> kafkaTemplate;

    public KafkaHealthIndicator(KafkaTemplate<String, String> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    @Override
    public Health health() {
        try {
            // lekki ping - metadata dla znanego topicu
            List<PartitionInfo> partitions =
                    kafkaTemplate.partitionsFor("orders.created");
            if (partitions == null || partitions.isEmpty()) {
                return Health.down().withDetail("reason", "No partitions").build();
            }
            return Health.up().build();
        } catch (Exception ex) {
            return Health.down(ex).build();
        }
    }
}

Taki wskaźnik pozwala szybko odróżnić problemy aplikacji od kłopotów z brokerem. W połączeniu z prostym alertem (np. w Slacku) daje wystarczające „wczesne ostrzeganie” bez potrzeby wdrażania dużych systemów APM na samym starcie projektu.

Trace’owanie przepływu zdarzeń

Kiedy liczba serwisów rośnie, równie ważne co metryki stają się ścieżki przepływu zdarzeń. Najprostsza wersja distributed tracingu w kontekście Kafki:

  • nagłówek z identyfikatorem korelacji (np. X-Correlation-Id),
  • propagacja tego ID w każdym kolejnym evencie,
  • logowanie ID w każdym serwisie (np. w MDC + loggerze).

Spring Kafka obsługuje nagłówki wiadomości, więc można je wstrzykiwać i odczytywać wprost:

@KafkaListener(topics = "orders.created")
public void handleOrder(
        @Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) String key,
        @Header("X-Correlation-Id") String correlationId,
        String payload) {

    MDC.put("correlationId", correlationId);
    try {
        // logika biznesowa
    } finally {
        MDC.remove("correlationId");
    }
}

To minimalistyczne podejście często wystarcza, by w logach zrekonstruować drogę pojedynczego zamówienia przez kilka serwisów, bez inwestycji w pełne narzędzia pokroju Jaeger/Zipkin już na pierwszym etapie.

Optymalizacja kosztów Kafki i Spring Boot w chmurze

Największe rachunki rzadko wynikają tylko z liczby mikroserwisów. Zwykle na koszty pracuje kombinacja: zbyt duże instancje, przesadna retencja danych, nadmiar topiców i niepotrzebne repliki.

Retencja danych i kompaktowanie logów

Utrzymywanie wszystkich eventów „na wieczność” ma sens głównie tam, gdzie Kafka pełni rolę surowego data lake. W typowym systemie transakcyjnym wystarczają ustawienia dopasowane do realnych potrzeb:

  • topic operacyjny (np. zamówienia) – retencja rzędu dni, ewentualnie tygodni,
  • topic audytowy – dłuższa retencja, ale z mniejszą liczbą partycji,
  • topic dla cache (np. snapshoty) – z włączonym log.compaction.

Kompaktowanie jest niedocenianym narzędziem kontroli kosztów. Dla topiców, gdzie przechowujemy „ostatni znany stan” encji (np. aktualny profil klienta per klucz), włączenie cleanup.policy=compact sprawia, że Kafka trzyma jeden rekord na klucz, zamiast całej historii zmian. W połączeniu z krótszą retencją czasową daje to spory zysk na przestrzeni dyskowej.

Rozmiar instancji vs ilość instancji

Spring Boot z natury nie jest ultra-„lekki”, ale można go rozsądnie odchudzić. Z perspektywy kosztów chmury często lepiej utrzymywać więcej małych instancji niż kilka dużych:

  • łatwiej skalować obciążenie stopniowo,
  • mniejszy wpływ awarii pojedynczej maszyny,
  • lepsze dopasowanie do autoscalingu (HPA w Kubernetes, ASG w AWS).

Jeżeli mikroserwis pełni głównie rolę konsumenta Kafki, zużycie CPU będzie falować wraz z ruchem. W takim przypadku warto:

  • obserwować realne użycie CPU/RAM przez kilka tygodni,
  • na tej podstawie obniżyć requests/limits (Kubernetes) lub rozmiar instancji,
  • dobudować proste reguły autoscalingu na podstawie lag-u albo CPU.

W praktyce wiele zespołów przepłaca za maszyny tylko dlatego, że domyślne konfiguracje „na start” nigdy nie zostały skorygowane po pierwszych pomiarach.

Managed Kafka kontra własny klaster

Utrzymywanie Kafka-clustra na własną rękę wydaje się tańsze, dopóki ktoś nie policzy czasu adminów i kosztów błędów. Przy niewielkiej skali i małym zespole często lepszy bilans wychodzi przy usługach typu Confluent Cloud, MSK czy odpowiedniki u innych dostawców.

Rozsądny kompromis:

  • na początku – managed Kafka z niskim tierem, do którego serwisy Spring Boot łączą się po TLS,
  • w środowiskach deweloperskich – lokalna Kafka w docker-compose lub Redpanda,
  • ewentualnie własny klaster dopiero wtedy, gdy:
    • wolumen danych faktycznie rośnie i rachunek od dostawcy „boli”,
    • zespół ma realne kompetencje i czas na administrację.

W wielu projektach ukrytym kosztem nie jest sama Kafka, ale dodatkowe komponenty: ZooKeeper (w starszych wersjach), monitoring, backupy, procesy odtwarzania po awarii. Managed usługa zmniejsza liczbę klocków, którymi trzeba się opiekować.

Modułowe podejście do mikroserwisów w Javie

Rozbijanie monolitu na dziesiątki małych aplikacji od pierwszego dnia rzadko jest opłacalne. Dla małego zespołu lepszą ścieżką bywa podejście modular monolith + stopniowa „ekstrakcja” serwisów do Spring Boot + Kafka.

Modularny monolit z przygotowanymi kontraktami zdarzeń

Jedna aplikacja Spring Boot z dobrze wydzielonymi modułami (np. jako niezależne pakiety, osobne maven/gradle modules) przyspiesza start, a przy tym pozwala od razu projektować zdarzenia:

  • logika domenowa znajduje się w modułach (np. orders-domain, payments-domain),
  • publikowanie i konsumpcja eventów dzieją się na początku „w pamięci” (np. prosty event bus),
  • kontrakty JSON/Avro są już zdefiniowane jako osobny moduł zależny.

Kiedy przychodzi moment skalowania, poszczególne moduły można „odciąć” jako osobne mikroserwisy. Kafka wchodzi wtedy jako infrastruktura do komunikacji, ale model domenowy i zdarzenia są już dobrze znane, przetestowane i nie trzeba ich wymyślać od nowa.

Wspólne biblioteki dla Kafki i Spring Boot

Utrzymywanie kilkunastu mikroserwisów bez wspólnych komponentów kończy się „wynajdywaniem koła” w każdym projekcie. Przy małym zespole opłaca się zainwestować w jedną lub dwie wewnętrzne biblioteki:

  • starter eventowy – zawiera:
    • konfigurację Spring Kafka (retry, DLQ, idempotencja),
    • bazowe klasy do publikowania eventów (np. abstrakcyjny DomainEventPublisher),
    • obsługę nagłówków korelacji i loggera.
  • moduł kontraktów – zawiera:
    • typy zdarzeń (Java + schema JSON/Avro),
    • wspólną walidację struktur,
    • ewentualnie testy kontraktowe między serwisami.

W praktyce każdy nowy mikroserwis sprowadza się wtedy do:

  1. dodania zależności do startera i kontraktów,
  2. skonfigurowania kilku właściwości (nazwa topicu, grupy konsumenckiej),
  3. zaimplementowania konkretnej logiki domenowej.

Dzięki temu ogranicza się powtarzalną konfigurację, a przy rozbudowie platformy inwestuje głównie w logikę biznesową, a nie w kopiowanie tego samego „kleju” integracyjnego między serwisami. Dodatkowo, zmiana podejścia do obsługi błędów, nagłówków, polityk retry czy formatów zdarzeń odbywa się w jednym miejscu, zamiast ręcznie aktualizować kilkanaście repozytoriów.

Taka para bibliotek nie musi być rozbudowana. Na początku często wystarczy kilka klas konfiguracyjnych, prosty EventPublisher z metrykami i bazowy interfejs słuchacza. Gdy zespół złapie rytm, dochodzą kolejne elementy: standard dla DLQ, rozszerzone logowanie, domyślne wzorce nazewnictwa topiców. Rozsądnie jest rozwijać je ewolucyjnie, reagując na realne problemy, a nie projektować „platformę do wszystkiego” przy trzech serwisach na krzyż.

Jeśli chcesz pójść krok dalej, pomocny może być też wpis: Implementacja systemu rekomendacji w Javie na bazie danych z Kafki.

Moduł kontraktów też nie musi być od razu spięty z pełnym schema registry. W wielu projektach startuje jako zwykły artefakt z klasami DTO + plikami JSON/Avro w katalogu resources. Dopiero gdy liczba zdarzeń i serwisów zaczyna rosnąć, dochodzą automatyczne testy kompatybilności schematów w pipeline CI oraz integracja z zewnętrznym rejestrem. Do tego czasu zespół zdąży już wypracować sensowny styl projektowania eventów, bez przepalania budżetu na zbyt wczesną formalizację.

Dobre podejście modularne i kilka wspólnych klocków pod Kafkę i Spring Boot sprawiają, że przejście z „budżetowego” monolitu do rozsądnie pociętej architektury mikroserwisowej nie wymaga rewolucji. Zespół może stopniowo dorzucać kolejne serwisy tam, gdzie faktycznie jest to uzasadnione skalą i niezawodnością, jednocześnie trzymając koszty chmury i utrzymania pod kontrolą.

Najczęściej zadawane pytania (FAQ)

Kiedy mikroserwisy w Javie ze Spring Boot i Kafka mają sens, a kiedy lepiej zostać przy monolicie?

Dla prostych systemów (kilka ekranów, jeden zespół, niewielki ruch) monolit jest zwykle tańszy, szybszy w budowie i łatwiejszy w utrzymaniu. Mikroserwisy zaczynają się opłacać dopiero wtedy, gdy realnie pojawia się problem skali, zwinności lub niezawodności – np. kilka zespołów rozwija równolegle różne obszary biznesowe albo poszczególne moduły mają zupełnie inne profile obciążenia.

Mikroserwisy zyskują przewagę, gdy trzeba niezależnie skalować komponenty, zapewnić wysoką dostępność (awaria jednego modułu nie wyłącza całości) i rozluźnić zależności technologiczne. Jeśli jednak kończy się na kilkunastu małych usługach „bo tak robią duzi”, przy małym biznesie zwykle przepala to budżet na DevOps, wersjonowanie API i debugowanie zamiast przynosić wartość.

Dlaczego używać Apache Kafka zamiast samego REST przy dużym ruchu między mikroserwisami?

Przy rosnącej liczbie usług i dużym ruchu REST zaczyna się dławić: pojawia się lawina synchronicznych połączeń, łańcuchy wywołań między serwisami, rośnie ryzyko timeoutów. Apache Kafka przenosi komunikację na zdarzenia – producent tylko zapisuje event do topica, a konsumenci przetwarzają go we własnym tempie. To zmniejsza sprzężenie czasowe i pozwala przeżyć piki ruchu bez dokładania kolejnych instancji wszystkiego.

Kafka ułatwia też skalowanie – zamiast rozbudowanej orkiestracji wystarczy zwiększyć liczbę partycji i konsumentów w grupie. Dodatkowy serwis bazujący na tych samych zdarzeniach można dołożyć bez zmian u nadawców. REST nadal ma miejsce przy interfejsach zewnętrznych czy prostych CRUD-ach, natomiast ciężki ruch i logika „w tle” znacznie taniej ogarnąć eventami.

Jaki jest realny koszt wejścia w mikroserwisy ze Spring Boot i Kafka przy ograniczonym budżecie?

Trzeba założyć wyższy próg wejścia niż przy monolicie: dochodzą kompetencje z architektury zdarzeniowej, obsługi Kafki, CI/CD, monitoringu i testów integracyjnych. To kosztuje czas i ludzi, nawet jeśli narzędzia wybierzesz w 100% open-source. Dlatego przy małym projekcie różnica między „zrobimy to jako monolit” a „zrobimy 10 mikroserwisów” potrafi być odczuwalna w każdym sprincie.

Da się jednak ciąć koszty na start. Zamiast pełnego Spring Cloud i komercyjnego monitoringu możesz użyć:

  • prostych plików YAML/Properties zasilanych zmiennymi środowiskowymi,
  • Prometheus + Grafana + ELK/EFK w podstawowej konfiguracji,
  • minimalnego procesu CI/CD opartego na Git + jeden pipeline „build & deploy”.

Klucz to nie luksusowa platforma, tylko zabezpieczenie się przed najdroższymi wpadkami: brak logów, brak metryk, brak prostego rollbacku wersji.

Jak sensownie podzielić system na mikroserwisy w Javie, żeby nie przepłacić za złą architekturę?

Zamiast dzielić według warstw technicznych („serwis do e‑maili”, „serwis do użytkowników”), lepiej bazować na domenie biznesowej i podejściu bounded context. Serwis odpowiada za spójny obszar znaczeniowy, np. „Zamówienia”, „Płatności”, „Katalog produktów”, „Reklamacje”. Granice można znaleźć, opisując kilka kluczowych ścieżek użytkownika i patrząc, gdzie zmienia się język pojęć i odpowiedzialność.

Na początek lepiej mieć mniej, ale sensowniej pociętych serwisów. Przebudowa kilku endpointów w jednym większym serwisie zwykle jest tańsza niż utrzymanie zbyt wielu drobnych usług z osobnymi bazami, migracjami i konfiguracją. Wielu zespołów dopiero po roku odkrywa, że „przestrzelili” z liczbą serwisów i zaczynają je scalać – to są kosztowne korekty.

Kiedy używać REST, a kiedy Kafki (event-driven) między mikroserwisami?

REST lub gRPC sprawdzają się wtedy, gdy użytkownik czeka na natychmiastową odpowiedź i zakres danych jest niewielki – logowanie, sprawdzenie salda, pobranie koszyka. Daje to prostą implementację, łatwe debugowanie i brak dodatkowych komponentów typu broker. Dobrze działa też przy krótkich łańcuchach 2–3 serwisów.

Kafka wygrywa, gdy:

  • wiele usług ma reagować na to samo zdarzenie (np. „płatność zatwierdzona” uruchamia raporty, księgowość i powiadomienia),
  • masz wysoki wolumen żądań, który trzeba buforować i rozkładać w czasie,
  • operacje są „niepilne” z punktu widzenia użytkownika (raporty, synchronizacje, integracje).

Praktyczne i budżetowe podejście to hybryda: HTTP na ścieżce użytkownika + zdarzenia do komunikacji w tle. Próba zrobienia wszystkiego synchronicznie albo całkowicie asynchronicznie zwykle winduje koszty.

Jakich błędów unikać przy wdrażaniu mikroserwisów z Kafką w małej firmie?

Najczęstsze pułapki to:

  • zbyt drobne cięcie na usługi (po kilkanaście serwisów przy małym systemie),
  • wpychanie Kafki wszędzie, nawet tam, gdzie prosty REST + baza w zupełności wystarczą,
  • brak sensownego logowania, monitoringu i śledzenia requestów między serwisami,
  • brak przemyślanej polityki retencji i partycji w Kafce.

Skutek: dłuższe wdrożenia, trudniejsze debugowanie, wyższe rachunki za chmurę i poczucie, że „architektura zjada projekt”.

Bezpieczniejsza ścieżka to start od prostszego rozwiązania (często monolit lub 2–3 większe serwisy), dołożenie Kafki tylko tam, gdzie faktycznie brakuje wydajności lub odporności, oraz stopniowe budowanie kompetencji zamiast rewolucji w pierwszym wydaniu.

Jakie minimum procesów i standardów API ustalić na starcie, żeby nie „utopić” się w chaosie?

Nawet przy jednym zespole opłaca się dogadać kilka rzeczy przed pierwszym deployem:

  • spójne nazwy serwisów i endpointów (np. orders-service, payments-service),
  • konwencję nazw topiców Kafki, np. orders.created, payments.completed,
  • proste wersjonowanie API: /api/v1/..., a zmiany niekompatybilne wprowadzane w /api/v2/...,
  • kontrakty HTTP (OpenAPI/Swagger) i schemy zdarzeń (Avro/JSON Schema) trzymane w jednym repozytorium.
Poprzedni artykułRecenzja płyty “Jazz dla dzieci”
Następny artykułKobiety w muzyce azjatyckiej – tradycja i nowoczesność
Renata Tomaszewska

Renata Tomaszewska to doświadczona terapeutka zajęciowa i pedagog, która w swojej pracy kładzie szczególny nacisk na muzykoterapię oraz wspieranie rozwoju emocjonalnego poprzez dźwięk. Jako ekspertka portalu Muzyka Dla Smyka, Renata przybliża czytelnikom techniki relaksacyjne oraz metody pracy z dziećmi o specjalnych potrzebach edukacyjnych. Jej artykuły to unikalne połączenie empatii z twardą wiedzą naukową o tym, jak wibracje i rytm wpływają na układ nerwowy młodego człowieka. Dzięki praktycznemu doświadczeniu klinicznemu, Renata pomaga rodzicom budować głęboką więź z dzieckiem w atmosferze akceptacji i twórczej radości. To autorytet w dziedzinie harmonijnego rozwoju dziecka poprzez sztukę.

Kontakt: renata_tomaszewska@muzykadlasmyka.edu.pl