Pokazywanie postów oznaczonych etykietą programming. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą programming. Pokaż wszystkie posty

poniedziałek, 1 września 2025

Agent AI w Javie: Spring AI + Docker Compose (krok po kroku)

Czy wiesz że w mniej niż godzinę zbudujesz i uruchomisz agenta AI? Agenta opartego o Spring AI, z wystawionym endpointem HTTP, gotowym do konteneryzacji i docker compose up. Pokażę Ci jak podłączyć narzędzia (Tools) w dwóch wariantach: własne metody oznaczone @Tool oraz serwer MCP (np. Brave/DuckDuckGo) w Compose.



Co zbudujemy

Wymagania

  • Java 21, Maven
  • Docker + Docker Compose
  • (Opcjonalnie) Klucz API do dostawcy modelu (np. OpenAI) – trzymamy w sekrecie

1) Inicjalizacja projektu

Najprościej zacząć od Spring Initializr – wybierz Spring Boot 3.4+, dodaj „Web” oraz startery Spring AI. Ręcznie w pom.xml możesz dodać:

<dependencies>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
    <version>1.0.1</version>
  </dependency>

  <!-- MCP klient, żeby podpiąć zewnętrzne serwery narzędzi (np. Brave/DuckDuckGo) -->
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
    <version>1.0.1</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>

Konfiguracja minimalna w src/main/resources/application.yml:

spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY:} # NIE trzymaj klucza w repo; wstrzykniemy go z Dockera
      chat:
        options:
          # Możesz wskazać model, np.:
          # model: gpt-4o-mini
          # temperature: 0.2

# (Opcjonalnie) MCP – rejestrujemy serwer zewnętrznych narzędzi
  # ai:
  #   mcp:
  #     clients:
  #       search:
  #         transport: http-sse
  #         endpoint: http://mcp-brave:8080/  # nazwa usługi z Compose
  #         auto-initialize: true

2) Minimalny agent: ChatClient + @Tool

Spring AI pozwala wystawić metody jako tools, po które może sięgnąć model w trakcie rozmowy (Function/Tool Calling). Na start zróbmy prosty tool zwracający „plan dnia” na podstawie promptu.

package dev.softwareveteran.agent;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

record ChatRequest(String message) {}
record ChatResponse(String content) {}

@Component
class PlanningTools {

  @Tool(name = "plan_day", description = "Tworzy krótki plan dnia na podstawie preferencji użytkownika.")
  public String planDay(String preferences) {
    // Tu mógłbyś podpiąć swoje API/kalendarz. Na razie demo.
    return """
      - 08:00 poranna kawa ☕
      - 09:00 deep work nad najważniejszym zadaniem
      - 12:30 szybki lunch
      - 14:00 spotkania
      - 17:30 sport/relaks
    """;
  }
}

@RestController
@RequestMapping("/chat")
class ChatController {

  private final ChatClient chat;

  ChatController(ChatClient.Builder builder, PlanningTools tools) {
    this.chat = builder
      .defaultSystem("Jesteś pomocnym asystentem architekta oprogramowania. " +
                     "Jeśli użytkownik pyta o plan dnia, skorzystaj z narzędzia 'plan_day'.")
      .tools(tools)   // <- .build="" chat="" chatrequest="" chatresponse="" code="" content="" equestbody="" new="" ool="" ostmapping="" public="" rejestrujemy="" req="" return="" var="">

Test lokalny:

./mvnw spring-boot:run
curl -X POST http://localhost:8080/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"Zaproponuj plan dnia dla pracy koncepcyjnej i sportu wieczorem"}'

3) (Opcjonalnie) Narzędzia przez MCP (np. web search)

Gdy potrzebujesz „prawdziwych” akcji – wyszukiwania w sieci, plików, pogody – nie musisz wszystkiego programować sam. MCP (Model Context Protocol) udostępnia gotowe serwery narzędzi, które Twój agent może wywoływać jak zwykłe tools. Wystarczy dodać starter klienta MCP i wskazać endpoint MCP w konfiguracji (sekcja powyżej), a następnie dorzucić poradnikowo prosty advisor w budowie klienta (Spring AI zrobi większość za Ciebie). W Compose dołożymy kontener z serwerem MCP, np. Brave Search lub DuckDuckGo.

4) Dockerfile – produkcyjny build Javy

# --- build stage ---
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -q -DskipTests dependency:go-offline
COPY src ./src
RUN mvn -q -DskipTests package

# --- runtime stage ---
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
# Zadbaj o sensowne limity pamięci w runtime:
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75"
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]

5) Docker Compose – jeden plik, cały stos

Przykładowy compose.yaml uruchamia aplikację oraz (opcjonalnie) serwer MCP Brave Search. Klucz API do modelu przekazujemy jako secret – bezpiecznie i poza obrazem.

name: spring-ai-agent

services:
  app:
    build: .
    image: swv/spring-ai-agent:latest
    ports:
      - "8080:8080"
    environment:
      # Spring AI pobierze klucz z env – nie wklejaj go do application.yml
      OPENAI_API_KEY_FILE: /run/secrets/openai_api_key
      # Jeśli korzystasz z MCP (sekcja application.yml):
      # SPRING_AI_MCP_CLIENTS_SEARCH_ENDPOINT: http://mcp-brave:8080/
    secrets:
      - openai_api_key
    depends_on:
      - mcp-brave
    # Opcjonalnie healthcheck
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 15s
      timeout: 3s
      retries: 10

  # (opcjonalnie) Serwer MCP Brave Search – wymaga BRAVE_API_KEY
  mcp-brave:
    image: shoofio/brave-search-mcp-sse:latest
    environment:
      BRAVE_API_KEY_FILE: /run/secrets/brave_api_key
      PORT: "8080"
    ports:
      - "18080:8080"
    secrets:
      - brave_api_key

secrets:
  openai_api_key:
    file: ./secret.openai-api-key
  brave_api_key:
    file: ./secret.brave-api-key

Uruchomienie:

# 1) Zapisz sekrety do plików (po jednym w linii)
echo "sk-...twoj-openai-key..." > secret.openai-api-key
echo "brv-...twoj-brave-key..." > secret.brave-api-key

# 2) Odpal całość
docker compose up --build

# 3) Test
curl -X POST http://localhost:8080/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"Znajdź najnowsze wieści o Spring AI i zrób 3-punktowe streszczenie"}'

Uwaga: jeśli nie chcesz MCP – usuń usługę mcp-brave i odpowiadające jej zmienne. Zamiast OpenAI możesz podłączyć lokalny model przez Docker Model Runner/Ollama – wtedy w konfiguracji Spring AI wybierz odpowiedniego providera i endpoint.

Co dalej?

  • Dodaj pamięć rozmowy i RAG (Vector Store) – Spring AI ma gotowe adaptery.
  • Dołóż drugi/zewnętrzny tool (np. pogodę/finanse) i pozwól modelowi decydować, którego użyć.
  • Wydziel agenta do osobnego mikroserwisu; dziel się nim przez HTTP/GRPC.

Przydatne linki

poniedziałek, 25 sierpnia 2025

AI Chatbot w Javie - RAG krok po kroku

Na bazie świetnego artykułu Baeldung o budowie chatbota w Javie z LangChain4j i MongoDB przygotowałem podobny, praktyczny przewodnik – ze wskazówkami, kodem i alternatywami. Oryginał znajdziesz tutaj: Building an AI Chatbot in Java With Langchain4j and MongoDB Atlas .



Dlaczego ten stack?

  • LangChain4j – wygodna warstwa do pracy z LLM w Javie (prompty, łańcuchy, pamięci, narzędzia).
  • MongoDB Atlas + Vector Search – trwałe przechowywanie embeddingów i szybkie zapytania semantyczne; idealne do RAG.

Efekt końcowy: prosty chatbot w stylu Q&A nad Twoimi dokumentami – z indeksem wektorowym w Atlasie, generacją embeddingów i łączeniem wyników z modelem językowym (RAG).

Wymagania wstępne

  • Java 17+
  • Konto w MongoDB Atlas i klaster (free tier wystarczy)
  • Klucz do dostawcy LLM/embeddingów (np. OpenAI) lub lokalny Ollama

Struktura projektu (Maven + Spring Boot)

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>5.1.0</version>
  </dependency>

  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>0.35.0</version>
  </dependency>

  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-openai</artifactId>
    <version>0.35.0</version>
  </dependency>

  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-mongodb</artifactId>
    <version>0.35.0</version>
  </dependency>
</dependencies>

Konfiguracja środowiska

server:
  port: 8080

app:
  mongo:
    uri: ${MONGODB_URI}
    db: ragdb
    collection: chunks
  openai:
    apiKey: ${OPENAI_API_KEY}
  rag:
    indexName: rag_vector_index
    dimensions: 1536

Definicja indeksu wektorowego

{
  "fields": [
    { "type": "vector", "path": "embedding", "numDimensions": 1536, "similarity": "cosine" },
    { "type": "string", "path": "source" },
    { "type": "string", "path": "text" }
  ]
}

Chunkowanie i embeddingi

Przykładowy kod Java generujący embeddingi i zapisujący do MongoDB:

float[] vec = embeddings.embed(chunk);
col.insertOne(new Document()
  .append("source", sourceName)
  .append("text", chunk)
  .append("embedding", toList(vec))
);

Retriever i RAG

Zapytanie semantyczne do Atlas Vector Search:

List<Bson> pipeline = List.of(
  Aggregates.vectorSearch("embedding", toList(qVec), indexName, 5, 200),
  Aggregates.project(Projections.include("text", "source"))
);

Łańcuch RAG z kontekstem i modelem czatu:

var messages = List.of(
  SystemMessage.from("Jesteś asystentem RAG..."),
  UserMessage.from("Kontekst:\n" + contextBlock + "\n\nPytanie: " + userQuestion)
);
return chat.generate(messages).content().text();

REST API

@PostMapping("/ingest")
public void ingest(@RequestBody IngestRequest req) {
  ing.ingest(req.source(), req.text());
}

@PostMapping
public ChatResponse chat(@RequestBody ChatRequest req) {
  return new ChatResponse(rag.ask(req.question()));
}

Alternatywy

Typowe pułapki

  1. Niedopasowany numDimensions w indeksie vs. model embeddingów.
  2. Brak overlapu przy chunkowaniu – tracony kontekst.
  3. Za duży kontekst – tnij do top-k i stosuj re-ranking.
  4. Sekrety w repo – używaj zmiennych środowiskowych.

Podsumowanie

W kilkudziesięciu linijkach kodu stworzyliśmy RAG chatbota – z MongoDB Atlas, LangChain4j i prostym REST API. Teraz możesz dodać streaming, cache embeddingów, czy personalizację dostępu.

Źródło, na którym bazowałem: Baeldung – Building an AI Chatbot in Java With Langchain4j and MongoDB Atlas

Dalsza lektura

poniedziałek, 18 sierpnia 2025

Nowe modele OpenAI gpt-oss – otwarte wagi i praktyczne wdrożenia AI

OpenAI wprowadza gpt-oss — serię modeli z otwartymi wagami, które możesz pobrać, uruchamiać lokalnie i dostosować do własnych potrzeb. To kolejny krok w kierunku większej kontroli nad sztuczną inteligencją, bez kompromisów w jakości rozumowania i elastyczności wdrożeń. Sprawdź, co oferują wersje 20b i 120b oraz jak szybko zacząć z nimi pracę.




1. O co chodzi z gpt‑oss?

gpt‑oss to rodzina modeli OpenAI z otwartymi wagami (open‑weight), które można pobierać, uruchamiać lokalnie i dostrajać pod własne przypadki użycia. Seria obejmuje dwa warianty: gpt‑oss‑120b (wyższa wydajność) oraz gpt‑oss‑20b (lżejszy, „edge/on‑device”).

  • Wydajność: 120b osiąga wyniki bliskie o4‑mini na kluczowych benchmarkach; 20b jest porównywalny do o3‑mini.
  • Wymagania sprzętowe: 120b działa na pojedynczej karcie 80 GB; 20b może działać przy ~16 GB pamięci.
  • Licencja: Apache 2.0 + zasady użycia gpt‑oss.
  • Przeznaczenie: rozumowanie, zadania agentyczne, elastyczne scenariusze developerskie.

2. Co dostajesz „w pudełku”

3. Jak to uruchomić w praktyce

Poniżej minimalny szkic — dopasuj do swojego środowiska:

# vLLM (przykład)
python -m vllm.entrypoints.openai.api_server \
  --model openai/gpt-oss-20b \
  --max-model-len 8192

# Zapytanie w stylu OpenAI Responses API (HTTP)
POST /v1/responses
{
  "model": "openai/gpt-oss-20b",
  "input": "Napisz krótkie streszczenie wpisu na blog."
}

Jeśli używasz Ollama, sprawdź gotowe pliki Modelfile i instrukcje z Cookbook/Guides. Do strumieniowania i pracy z narzędziami wykorzystaj format Harmony, który odwzorowuje zachowanie Responses API.

4. Zastosowania i wzorce

  • Na urządzeniu / edge: 20b do asystentów offline, automatyzacji IDE, podsumowań i ekstrakcji danych.
  • Serwerowo: 120b do pipeline’ów analitycznych, agentów wykonujących wieloetapowe zadania i klasycznych RAG.
  • Dostrajanie: klasyczne fine‑tuning + taktyki optymalizacji (LoRA/QLoRA, 4‑bit), gotowe „recipes”.

5. Bezpieczeństwo i zgodność

Modele gpt‑oss przeszły szkolenia i ewaluacje bezpieczeństwa (w tym testy zgodne z Preparedness Framework). Zadbano o redukcję ryzyk i zgodność z dobrymi praktykami — to ważne przy wdrożeniach komercyjnych.

6. Szybki start — checklista

  1. Pobierz wagi 20b lub 120b i sprawdź wymagania sprzętowe.
  2. Wybierz runtime (Ollama, vLLM, Transformers) i uruchom endpoint kompatybilny z OpenAI API.
  3. Włącz Harmony dla stabilnego formatowania odpowiedzi i integracji narzędzi.
  4. Skonfiguruj logowanie, limity tokenów, caching i monitorowanie.
  5. Jeśli potrzebujesz — wykonaj fine‑tuning na własnych danych zgodnie z „recipes”.

7. Podsumowanie

gpt‑oss to pragmatyczne otwarcie: realne wagi, solidne wyniki i komplet materiałów do wdrożeń. Dla developerów oznacza to większą kontrolę kosztów, szybszą iterację i możliwość działania lokalnie — bez rezygnacji z jakości rozumowania.

poniedziałek, 21 lipca 2025

EA 12 factor app - fundament dla nowoczesnych aplikacji chmurowych czy przestarzały hype?

 W poprzednim wpisie „EA: 12 Factor Agents — Nowe podejście, które może zmienić Twoje myślenie o AI” zaproponowałem przeniesienie idei 12 Factor App na grunt architektury agentowej. Czas wrócić do źródeł i przyjrzeć się oryginalnemu manifestowi 12factor.net, który — mimo że powstał ponad dekadę temu — wciąż stanowi solidny fundament dla budowania nowoczesnych aplikacji chmurowych, mikrousług i systemów agentowych.



Dlaczego warto znać 12 Factor App?

Zasady 12 Factor App to nie przepis na framework czy technologię, ale zestaw praktyk projektowych, które pomagają budować aplikacje:

  • przenośne (cloud-native),

  • skalowalne poziomo,

  • łatwe do wdrażania, monitorowania i rozwijania.

W świecie, w którym infrastruktura staje się zautomatyzowana, a aplikacje coraz bardziej rozproszone, te zasady okazują się nie tyle dobrowolnymi rekomendacjami, co warunkiem „przeżycia” w produkcji.

12 czynników — w skrócie i z komentarzem architekta

Przyjrzyjmy się krótko każdemu z 12 czynników, z perspektywy osoby projektującej systemy rozproszone, często hybrydowe, integrujące się z agentami, API, systemami kolejkowymi i narzędziami DevOps.

1. Codebase — jedna baza kodu na aplikację

Każdy serwis powinien mieć jedno źródło prawdy (repozytorium Git). W przypadku systemów agentowych może to oznaczać, że agent to również jednostka wdrożeniowa (np. kontener), a nie biblioteka współdzielona przez inne serwisy.

2. Dependencies — jawne zarządzanie zależnościami

Brak polegania na globalnym środowisku (jak systemowe paczki). W świecie micro-agents oznacza to np. unikanie ukrytych zależności między agentami a platformą hostującą.

3. Config — konfiguracja przez zmienne środowiskowe

Oddzielenie kodu od konfiguracji to baza dla portowalności i automatyzacji — zarówno w CI/CD, jak i w orkiestracji (Kubernetes, Nomad).

4. Backing services — traktuj zewnętrzne zasoby jak attachable

Usługi takie jak bazy danych, kolejki, pamięci cache są zależnościami, nie integralną częścią aplikacji. Ułatwia to testowanie, replikację, skalowanie.

5. Build, release, run — rozdzielenie etapów cyklu życia aplikacji

Oddzielenie builda (np. Docker image), release'u (konfiguracja + build) i uruchomienia (run-time). Kluczowe przy wersjonowaniu agentów i rollbackach.

6. Processes — bezstanowe procesy

Statelessness to kręgosłup skalowalności i niezawodności. Dane sesyjne powinny trafić do Redis, a nie do pamięci agenta.

7. Port binding — self-contained aplikacje nasłuchujące na porcie

Każda aplikacja (lub agent) powinna być samodzielna i gotowa do uruchomienia w dowolnym środowisku przez proste docker run.

8. Concurrency — skalowanie przez procesy

Nie chodzi tylko o wielowątkowość, ale o świadome projektowanie poziomów równoległości (np. web, worker, cron). W systemach agentowych: osobne procesy dla agentów odpowiedzialnych za inne zadania.

9. Disposability — szybki start i czyste wyłączanie

Aplikacja (agent) powinna uruchamiać się i zamykać szybko i bezpiecznie. Przydatne w autoskalowaniu i orchestracji (K8s, ECS).

10. Dev/prod parity — minimalizacja różnic środowiskowych

Im mniejsza różnica między lokalnym docker-compose up a produkcją w chmurze, tym mniej „niespodzianek”. Infrastructure as Code to tu podstawa.

11. Logs — traktuj logi jako strumień zdarzeń

Nie zapisuj logów do pliku — logi mają iść na stdout/stderr, by mogły być przechwycone przez ELK, Loki, czy inny system monitorujący.

12. Admin processes — pomocnicze zadania jako jednorazowe procesy

Migracje bazy, inspekcja danych czy czyszczenie cache — wszystko jako osobne, uruchamialne procesy. Idealne do CRON-jobów lub zadań DevOps.

12 Factor App vs. 12 Factor Agents

W podejściu agentowym mówimy o architekturze zorientowanej na samodzielne, autonomiczne komponenty komunikujące się ze sobą. W tym świetle:

  • Agent może być procesem zgodnym z 12FA.

  • Platforma dla agentów powinna wspierać lifecycle wg 12FA (deployment, logi, konfiguracja).

  • Rozproszenie i niezależność agentów staje się naturalnym rozszerzeniem idei „self-contained apps”.

W praktyce — 12 Factor App to doskonały fundament, na którym możemy budować 12 Factor Agents.

Kiedy 12 Factor App się nie sprawdzi?

Oczywiście, jak każde podejście, 12FA ma swoje ograniczenia:

  • Trudno je zastosować do monolitów z dużym stanem wewnętrznym (ERP, legacy).

  • Nie nadaje się do aplikacji wymagających silnego stateful compute (np. stream processing bez zewnętrznego stanu).

  • Wymaga kultury DevOps i automatyzacji — bez tego wiele założeń się nie obroni.

Podsumowanie

Zasady 12 Factor App pozostają aktualne, zwłaszcza jako podstawa dla nowoczesnych, autonomicznych komponentów — w tym agentów. W połączeniu z architekturą agentową stanowią przepis na systemy:

  • elastyczne i odporne na zmiany,

  • łatwe do wdrażania i skalowania,

  • zgodne z filozofią „as a Service”.

Jeśli jeszcze nie stosujesz 12FA — warto zacząć chociażby od rozdzielenia konfiguracji, logów i budowania obrazów aplikacyjnych. Małe kroki, wielki zysk.


Czy Twój system jest zgodny z 12 Factor App? A może agenci w Twojej organizacji mogliby na tym podejściu skorzystać? Daj znać w komentarzach lub na LinkedInie — chętnie podyskutuję.


poniedziałek, 2 czerwca 2025

dev{tools}: Para – lekka platforma backendowa do tworzenia aplikacji i API

Jeśli potrzebujesz prostego sposobu na szybkie uruchomienie backendu dla swojej aplikacji — bez budowania wszystkiego od zera — Para może być dokładnie tym, czego szukasz.

To lekki framework backendowy typu open source, który pozwala w kilka minut zbudować REST API, zarządzać danymi i obsługiwać uwierzytelnianie — bez konieczności pisania setek linii kodu infrastrukturalnego.



Czym jest Para?

Para to backend oparty o Java i Spring Boot, który działa w duchu „backend-as-a-service”.
Umożliwia łatwe tworzenie i zarządzanie obiektami danych, integrację z aplikacjami frontendowymi oraz uruchamianie go lokalnie lub w chmurze.

Ciekawostka: choć niektóre źródła rozwijają nazwę jako Pluggable and Reusable Architecture, autor projektu wskazuje, że pochodzi ona od bułgarskiego słowa „pára” (para wodna) – symbolicznie „zasilająca” backend Twojej aplikacji.
➡️ Źródło – GitHub README

Najważniejsze cechy Para:

  • przechowuje i zarządza obiektami (modelami) w stylu NoSQL,

  • zapewnia gotowe mechanizmy uwierzytelniania i RBAC,

  • wspiera relacje między obiektami,

  • udostępnia RESTful API natychmiast po wdrożeniu,

  • obsługuje tagowanie, wyszukiwanie i wersjonowanie danych.

📘 Oficjalna dokumentacja: https://paraio.org/docs/

Z czego się składa?

KomponentOpis
para-coreSilnik backendu (Java) zarządzający danymi, API i logiką.
para-serverSamodzielna aplikacja REST oparta na Spring Boot.
para-clientKlient Java do komunikacji z API Para.
para-jsKlient JS/TS do integracji z aplikacjami frontendowymi (np. React).
para-admin-uiGraficzny panel administracyjny oparty na Angular.

Architektura i komponenty

System Para składa się z kilku współpracujących warstw:

  • Para Server – główny punkt wejścia (Spring Boot), udostępniający REST API.

  • Para Core – logika obiektów, typów, zabezpieczeń i relacji.

  • Storage Layer – przechowywanie danych (np. ElasticSearch, DynamoDB, H2).

  • Client SDKs – integracja z frontendem lub backendem (JS/Java).

  • Admin UI – panel do zarządzania modelami i konfiguracją.

Dzięki modułowości możesz wymienić lub rozszerzyć każdy z komponentów — np. podmienić bazę danych, system logowania, dodać middleware.

Do czego mogę użyć Para?

  • budowa CMS-ów, katalogów, blogów, e-commerce i prostych aplikacji,

  • szybkie tworzenie REST API dla aplikacji mobilnych i SPA,

  • backend do MVP, proof-of-concept, hackathonów,

  • mikroserwis z autoryzacją i relacjami obiektów,

  • backend dla aplikacji JAMstack (React/Vue + Para + CDN).

Jak zacząć? (Quickstart)

1. Uruchomienie Para lokalnie (Docker)


docker run -p 8080:8080 --name para \ -e PARA_ENV=dev \ -e PARA_APP_NAME=app \ -e PARA_SECRET_KEY=mysecret \ erudika/para

Po uruchomieniu REST API będzie dostępne pod adresem:
📍 http://localhost:8080


2. Tworzenie obiektów

📌 Upewnij się, że posiadasz poprawny token JWT (np. po logowaniu).


curl -X POST http://localhost:8080/v1/app/object \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR-JWT-TOKEN" \ -d '{ "type": "person", "name": "Jan Kowalski", "email": "jan@example.com" }'

Otrzymasz obiekt z id, timestamp, metadanymi i możliwością dalszej edycji przez API.


3. Użycie z JavaScript (np. w React)


import { ParaClient } from '@erudika/para-client'; const para = new ParaClient('accessKey', 'secretKey'); para.getAll('person').then(results => { console.log(results); });

📌 Domyślnie dane są paginowane — jeśli chcesz uzyskać więcej wyników, pamiętaj o ustawieniu limitu.

Wbudowane funkcje bezpieczeństwa

  • uwierzytelnianie (OAuth2, Google, GitHub, Facebook),

  • RBAC – role i uprawnienia,

  • szyfrowanie haseł, tokeny JWT,

  • full-text search (ElasticSearch / Lucene),

  • tagowanie i relacje między obiektami,

  • wsparcie dla middleware i hooków (Spring Boot).

Integracja z architekturą

Para działa jako mikroserwis REST – możesz zintegrować go z:

  • frontendem (React, Vue, Angular),

  • systemami auth (np. Keycloak, Auth0),

  • CI/CD (np. GitHub Actions),

  • backendami Node.js, Spring Boot lub .NET.

Wersja self-hosted działa na Dockerze, VPS lub w K8s — masz pełną kontrolę nad danymi i środowiskiem.

Podsumowanie

Para to backend idealny do szybkich wdrożeń, prototypów i lekkich aplikacji.
Daje Ci REST API, uwierzytelnianie, przechowywanie danych i relacje — bez konieczności budowania tego wszystkiego od zera.

✅ REST API w kilka minut
✅ Możliwość pełnej kontroli (self-hosting)
✅ Integracja z frontendem i CI/CD
✅ Bezpieczna architektura oparta o Java/Spring
✅ Gotowe do użycia jako komponent w mikroserwisach

poniedziałek, 28 kwietnia 2025

Wzorce MSA - Testy kontraktowe i Consumer-Driven Contract

 W architekturze mikroserwisów komunikacja między usługami to chleb powszedni. Serwis A woła B, B woła C, a C czasem pyta jeszcze o coś D... I wszystko działa — do pierwszej zmiany w jednym z tych komponentów.



Zamiast "to się chyba uda", lepiej postawić na testy kontraktowe, a konkretnie na podejście Consumer-Driven Contract (CDC). To technika, która pozwala upewnić się, że komunikujące się systemy rozumieją się bez niedomówień.

Czym jest test kontraktowy?

To umowa między usługą kliencką (consumer) a usługą dostarczającą dane (provider).
Kontrakt opisuje co dokładnie oczekuje klient, a testy sprawdzają, czy dostawca spełnia te wymagania.

Nie chodzi tu o pełne testy integracyjne – tylko o weryfikację zachowania w określonych scenariuszach komunikacyjnych.

Consumer-Driven Contract (CDC) – o co chodzi?

W podejściu CDC:

  1. Klient definiuje kontrakt – np. jaką strukturę odpowiedzi HTTP oczekuje po zapytaniu GET /orders/{id}.

  2. Kontrakt jest zapisywany jako artefakt (np. plik .json, .yml, .pact).

  3. Dostawca (provider) implementuje API, które musi spełniać oczekiwania konsumentów.

  4. W CI/CD kontrakt jest weryfikowany po stronie providera, by uniknąć regresji.

Przykład (Pact)

Załóżmy, że aplikacja frontendowa (consumer) oczekuje takiej odpowiedzi z serwisu zamówień:


{ "id": "abc123", "status": "DELIVERED", "total": 99.99 }

Konsument definiuje kontrakt:


const provider = new Pact({ consumer: 'Frontend', provider: 'OrderService', }); provider .given('Order exists') .uponReceiving('a request for order details') .withRequest({ method: 'GET', path: '/orders/abc123', }) .willRespondWith({ status: 200, body: { id: like('abc123'), status: like('DELIVERED'), total: like(99.99), }, });

📤 Kontrakt jest publikowany do brokera (np. Pactflow), a provider w swoim CI odpala testy, które go weryfikują.

Dlaczego warto?

  • Zwiększasz pewność, że nie złamiesz kompatybilności

  • Wcześnie wykrywasz konflikty między zespołami

  • Odchudzasz testy E2E (bo nie musisz testować wszystkiego z wszystkimi)

  • Automatyzujesz weryfikację zgodności API

  • Ułatwiasz niezależne wdrażanie mikroserwisów

Typowe problemy bez CDC

  • 🔴 Zmiana API w jednym mikroserwisie powoduje błędy w innym (niespójność)

  • 🔴 Brak testów integracyjnych → produkcja testuje za nas

  • 🔴 "Ale przecież dokumentacja mówiła inaczej..."

  • 🔴 Trudność w wersjonowaniu i utrzymaniu API

Popularne narzędzia CDC

NarzędzieJęzyk / EkosystemUwagi
PactJava, JS, Python, .NETNajpopularniejsze narzędzie CDC
Spring Cloud ContractJavaIntegracja z Spring Boot
HoverflyHTTP proxy, języki dowolneSimulacja usług w testach
ContractTestJS, RESTLekkie testy kontraktowe

Jak wdrożyć CDC w praktyce

  1. Zdefiniuj kontrakt po stronie klienta (frontend, inny mikroserwis)

  2. Publikuj kontrakt do repozytorium (broker)

  3. W CI providera uruchamiaj testy weryfikujące kontrakt

  4. Nie wdrażaj providerów, którzy łamią oczekiwania klientów

  5. Automatyzuj ten proces w CI/CD

Podsumowanie

Testy kontraktowe i podejście CDC to must-have w ekosystemie mikroserwisów:

🔹 Gwarantują kompatybilność usług
🔹 Ułatwiają komunikację między zespołami
🔹 Wzmacniają kulturę DevOps i ciągłą integrację
🔹 Ograniczają chaos wersjonowania API

poniedziałek, 21 kwietnia 2025

EA - Coupling i Kohezja

Każdy programista słyszał o zasadach dobrego kodu: czytelność, modularność, łatwość testowania, niskie koszty utrzymania. Ale jak je osiągnąć?



Jednym z fundamentów są dwa niepozorne, lecz kluczowe pojęcia: kohezja (cohesion) i sprzężenie (coupling).

W tym artykule wyjaśnię, czym są, jak wpływają na jakość systemu i pokażę praktyczne przykłady.

Coupling (sprzężenie)

Sprzężenie to miara zależności między modułami/kontrolerami/klasami.

  • Niskie sprzężenie (loose coupling) – moduły są niezależne, zmiana jednego nie powoduje efektu domina.

  • Wysokie sprzężenie (tight coupling) – zmiana w jednym komponencie wymusza zmiany w wielu innych, co utrudnia rozwój i testowanie.

Przykład wysokiego sprzężenia:


class OrderService { private final EmailService emailService = new EmailService(); public void placeOrder(Order order) { // logika zamówienia emailService.sendConfirmation(order.getUserEmail()); } }

OrderService tworzy instancję EmailService i staje się od niej zależny. Nie można tego łatwo przetestować, wymienić implementacji, ani odseparować.

Przykład niskiego sprzężenia (dobrze):


class OrderService { private final EmailService emailService; public OrderService(EmailService emailService) { this.emailService = emailService; } public void placeOrder(Order order) { // logika zamówienia emailService.sendConfirmation(order.getUserEmail()); } }

Tu zastosowano wstrzykiwanie zależności (Dependency Injection). OrderService nie obchodzi, jaka to implementacja EmailService. Dzięki temu łatwiej pisać testy, refaktoryzować i utrzymywać kod.

Cohesion (kohezja, spójność)

Kohezja to miara jak bardzo odpowiedzialności danego modułu są ze sobą powiązane.

  • Wysoka kohezja – klasa/metoda ma jedno konkretne zadanie.

  • Niska kohezja – klasa robi wszystko: zapisuje dane, wysyła e-maile, liczy podatki i jeszcze zapisuje logi...

Przykład niskiej kohezji:


class ReportManager { public void generateReport() { /* ... */ } public void sendReportByEmail() { /* ... */ } public void logReportToFile() { /* ... */ } public void calculateRevenue() { /* ... */ } }

Ta klasa ma wiele odpowiedzialności. Trudno ją rozszerzyć lub przetestować.

Przykład wysokiej kohezji (dobrze):


class ReportGenerator { public Report generate() { /* ... */ } } class ReportSender { public void send(Report report) { /* ... */ } } class RevenueCalculator { public BigDecimal calculate(Report report) { /* ... */ } }

Każda klasa ma jedno zadanie (Single Responsibility Principle) i dobrze współpracuje z innymi – ale nie jest z nimi nadmiernie powiązana. To przykład wysokiej kohezji i niskiego sprzężenia – złotego standardu projektowania.

Dlaczego to ważne?

Cecha systemuNiskie sprzężenieWysoka kohezja
Testowalność✅ Łatwa✅ Łatwa
Rozszerzalność✅ Modułowa✅ Przewidywalna
Refaktoryzacja✅ Bezpieczna✅ Czysta
Zrozumiałość kodu✅ Izolowana✅ Logiczna

Jak to osiągnąć?

✅ Stosuj wstrzykiwanie zależności
✅ Dziel kod zgodnie z zasadą jednej odpowiedzialności (SRP)
✅ Używaj wzorców projektowych (np. Strategy, Adapter)
✅ Grupuj klasy zgodnie z kontekstem domeny (np. DDD)
✅ Refaktoruj, gdy widzisz "kuchnię z tysiącem zadań"

Podsumowanie

  • Coupling to pytanie: jak bardzo zależysz od innych?

  • Cohesion to pytanie: czy robisz jedną rzecz dobrze, czy wszystko po trochu?

Dążymy do niskiego sprzężenia i wysokiej kohezji – to podstawa dobrze zaprojektowanych, testowalnych i skalowalnych systemów.

poniedziałek, 7 kwietnia 2025

Wzorce MSA - Distributed Tracing B3 propagation

 W architekturze mikroserwisów każdy system to zbiór wielu mniejszych komponentów, które komunikują się między sobą najczęściej za pomocą żądań HTTP, wiadomości z kolejek lub gRPC. To świetnie wspiera skalowalność i elastyczność… dopóki coś nie przestanie działać.


Wtedy pojawia się pytanie:
  • Gdzie utknęło żądanie?
  • Który mikroserwis zawiódł?
  • Jak długo trwało przetwarzanie w każdym kroku?

Distributed Tracing to wzorzec, który pozwala śledzić żądania przez cały łańcuch mikroserwisów, a standard B3 umożliwia łatwą i jednolitą identyfikację każdego kroku tej podróży.

Na czym polega Distributed Tracing?

Distributed Tracing (śledzenie rozproszone) to mechanizm, który rejestruje ścieżkę żądania przez wiele usług. Każde żądanie otrzymuje unikalny identyfikator, który jest przekazywany między usługami — dzięki temu możemy odtworzyć całą trasę żądania i zmierzyć czas trwania każdego kroku.

Wprowadzenie do standardu B3

B3  to lekki, prosty do wdrożenia standard propagowania trace-id w systemach rozproszonych. Wystarczy dodać kilka nagłówków HTTP, by rozpocząć śledzenie żądań.

Kluczowe nagłówki B3:

NagłówekOpis
X-B3-TraceIdUnikalny identyfikator całego śledzenia (trace)
X-B3-SpanIdIdentyfikator konkretnego kroku (span) w ramach trace
X-B3-ParentSpanId(opcjonalnie) identyfikator nadrzędnego span
X-B3-SampledCzy trace ma być zarejestrowany (1 = tak)
X-B3-FlagsDodatkowa flaga debugowania

Przykład nagłówków B3 w żądaniu HTTP:


X-B3-TraceId: 4bf92f3577b34da6a3ce929d0e0e4736 X-B3-SpanId: 00f067aa0ba902b7 X-B3-ParentSpanId: b73a56a1d2ee0eb2 X-B3-Sampled: 1

Jak to działa w praktyce?

Schemat przepływu

Wyobraźmy sobie system z trzema mikroserwisami:


Klient → Gateway → Serwis A → Serwis B → Serwis C

Dzięki nagłówkom B3:

  1. Klient wysyła żądanie do Gateway z X-B3-TraceId.

  2. Gateway tworzy X-B3-SpanId i wysyła je do Serwisu A.

  3. Serwis A generuje swój SpanId, zachowuje ParentSpanId z Gatewaya i przekazuje trace dalej.

  4. Na końcu, Serwis C kończy trace — a wszystkie kroki są zarejestrowane.

Wizualizacja śladu (trace)

Po zebraniu wszystkich danych (np. za pomocą Jaeger, Zipkin lub Grafana Tempo), możemy zobaczyć wizualnie:


TraceId: 4bf92f3577b34da6a3ce929d0e0e4736 ├── Gateway [15 ms] │ └── Serwis A [30 ms] │ └── Serwis B [40 ms] │ └── Serwis C [25 ms]

Implementacja Distributed Tracing z użyciem Spring Boot i Sleuth


<!-- pom.xml --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>

# application.yml spring: zipkin: base-url: http://zipkin:9411 sleuth: sampler: probability: 1.0

Dzięki Sleuth i Zipkin aplikacja automatycznie:

  • generuje trace-id i span-id,

  • przekazuje nagłówki HTTP,

  • wysyła dane do Zipkina.

Korzyści z wdrożenia Distributed Tracing

Szybsze diagnozowanie problemów
Pomiar czasu odpowiedzi poszczególnych usług
Widoczność zależności między mikroserwisami
Identyfikacja wąskich gardeł
Lepsze wsparcie operacyjne i DevOps


Wnioski i dobre praktyki

  • Zawsze przekazuj trace-id i span-id w żądaniach HTTP, gRPC i eventach.
  • Testuj czy wszystkie mikroserwisy poprawnie obsługują nagłówki B3.
  • Używaj narzędzi jak Zipkin, Jaeger lub Grafana Tempo do analizy trace’ów.
  • W systemach rozproszonych bez observability jesteś ślepy.


Podsumowanie

Wzorzec Distributed Tracing to fundament nowoczesnych, obserwowalnych systemów mikroserwisowych. W połączeniu ze standardem B3 umożliwia łatwą propagację identyfikatorów śledzenia między usługami. Dobrze wdrożony trace pozwala nie tylko znaleźć błędy, ale też optymalizować wydajność i rozumieć złożoność architektury.


poniedziałek, 31 marca 2025

Wzorce MSA — Historia Darka i pewnego mikroserwisu

Darek był doświadczonym programistą backendowym. Pracował nad dużą aplikacją monolityczną obsługującą klientów w branży e-commerce. Kod miał swoje lata, swoje kruczki, ale ogólnie był w porządku — przynajmniej dopóki się nie psuł. A psuł się coraz częściej.



Pewnego dnia Darek postanowił, że nadszedł czas na zmiany. “Ten moduł płatności... aż się prosi, żeby go wydzielić do osobnego mikroserwisu!” — pomyślał.

Refaktoryzacja. Decoupling. Swoboda wdrożeń. Skalowalność. Brzmi pięknie. Co mogłoby pójść nie tak?

Przejście z komunikacji lokalnej do sieciowej

W monolicie wszystko było proste. Jedna metoda wywoływała drugą. Dane wędrowały po stosie w tym samym procesie JVM. Teraz — po wydzieleniu płatności do osobnego serwisu — zaczęło się dziać coś nowego. Darek wystawił endpoint REST i zaczął go wołać z głównego systemu.

Na lokalnym hoście działało to świetnie. Jednak po wdrożeniu na środowisko testowe okazało się, że...

DNS nie rozwiązuje się tak, jak Darek myślał.

Raz działał adres payment-service, raz payment.internal.local, innym razem... nie działał wcale. “Dlaczego curl działa, a HttpClient wyrzuca timeout?” — pytał ze złością. Zrozumiał, że sieć rządzi się swoimi prawami. I że trzeba się zaprzyjaźnić z service discovery.

Opóźnienia i retry

Wcześniej wszystko było instant. Teraz? Zdarzało się, że płatność odpowiadała po sekundzie, a czasem wcale. Timeout. I znowu.

“No to dorzucę retry” — pomyślał. I tak zrobił. Tyle że teraz system zaczął... powielać żądania. Dwukrotne opłaty, chaos w logach, sfrustrowani testerzy.

Nauczył się, że retry musi być idempotentne, a najlepiej opatrzone unikalnym ID żądania.

Circuit Breaker i fallback

Retry to nie wszystko. Mikroserwis płatności zaczął się czasem zawieszać przy dużym obciążeniu. A główny system... zawieszał się razem z nim.

Wtedy Darek odkrył Circuit Breaker. Dodał warstwę zabezpieczającą, która w razie błędu "odcinała" płatności i wrzucała je do kolejki z komunikatem: “usługa chwilowo niedostępna, spróbuj później”. Uratowało to resztę aplikacji.

Samodzielne wdrożenie... i nowe wyzwania

Wydzielenie mikroserwisu oznaczało możliwość niezależnego wdrażania kodu. Super. Do czasu.

Po wdrożeniu nowej wersji płatności logi z głównego systemu zaczęły krzyczeć: “500 Internal Server Error”. Co się okazało?

Nowa wersja zmieniła kontrakt API. Brak zgodności. Brak komunikacji między zespołami. Brak testów integracyjnych.

Darek nauczył się, że niezależność mikroserwisów nie oznacza dowolności. Kontrakty API muszą być święte — albo przynajmniej wersjonowane.

Brak kontraktu... czyli gorzkie lekcje integracji

W pewnym momencie frontend został zmodyfikowany tak, aby korzystał z nowej wersji mikroserwisu płatności. Ale… mikroserwis miał już też innych klientów. Nagle okazało się, że jedna z aplikacji mobilnych nie działa — nowy endpoint miał inny format odpowiedzi.

Darek odkrył wtedy Consumer-Driven Contract (CDC) — podejście, w którym to konsumenci mikroserwisu definiują oczekiwania wobec API, a producent (czyli mikroserwis) weryfikuje zgodność przy każdej zmianie. Narzędzia takie jak Pact pozwalają testować te kontrakty automatycznie w CI/CD.

💡 „Gdybyśmy mieli kontrakty konsumenckie wcześniej — uniknęlibyśmy błędów na produkcji i nieporozumień z zespołem mobilnym” – przyznał później Darek.

Monitoring, tracing i chaos w logach

Z czasem pojawiło się więcej mikroserwisów. I więcej pytań:

  • Gdzie utknęło żądanie?

  • Dlaczego płatność trwała 4 sekundy?

  • Kto wysłał ten dziwny request?

Logi z jednego serwisu przestały wystarczać. Darek wdrożył centralny monitoring (ELK Stack), a potem distributed tracing (np. Jaeger). I zrozumiał, że bez identyfikatora correlation-id w nagłówkach niczego nie da się poskładać.

DevOps

W miarę jak pojawiało się więcej mikroserwisów, więcej pipeline’ów CI/CD, więcej testów, Darek zrozumiał, że same zmiany w kodzie to za mało.

🔹 Musiał przygotować procesy wdrożeniowe z podziałem na środowiska.
🔹 Zautomatyzować testy integracyjne i rollback w przypadku błędów.
🔹 Wdrożyć monitoring stanu zdrowia usług (healthcheck, readiness, liveness).
🔹 I wreszcie — spiąć to wszystko z GitLab CI i ArgoCD.

„Mikroserwisy bez kultury DevOps to jak wyścigówka bez kierowcy – teoretycznie szybka, ale łatwo o kraksę.”

Skalowalność i autoscaling

Ruch wzrósł. Płatności musiały działać szybko, więc dorzucono autoscaling w Kubernetesie. Ale... przy skoku ruchu nowe instancje nie miały cache’a i ładowały dane z opóźnieniem.

Nauczył się, że skalowalność pozioma nie rozwiązuje wszystkiego, jeśli nie pomyślisz o stanie aplikacji, cache’ach i bazach danych.

Podsumowanie: Mikroserwisy? To nie tylko podział na pliki

Darek zaczął od pomysłu: "podzielmy system, będzie lepiej".

Ale każdy krok — DNS, retry, service discovery, tracing, versioning, deployment, skalowanie — wymagał świadomości, planowania i odpowiedzialności.

💡 Mikroserwisy to nie tylko “dzielenie systemu na kawałki”. To świadome projektowanie rozproszonego ekosystemu z wszystkimi jego pułapkami i możliwościami.


„Dziś, gdy ktoś mówi mi, że ‘chce sobie coś wydzielić’, pytam: ‘czy jesteś gotów na konsekwencje?’” – śmieje się Darek, patrząc na swój dashboard Prometheusa.


 


Część praktyk i wzorców zdążyłeś już poznać w moich poprzednich wpisach. Kolejne będą się pojawiać w następnych. Więc zapraszam do czytania i śledzenia mojego bloga!