Ordify — Arhitektura¶
Detaljan tehnički opis Ordify platforme: servisi, modeli, API, obrasci i planovi.
1. Pregled servisa¶
| Servis | Port | Baza | Zaduženje |
|---|---|---|---|
| ordify-menu-service | 5006 | meni | Meni: proizvodi, kategorije, cene, dostupnost |
| ordify-order-service | 5007 | porudzbine | Biznis logika: korpa, porudžbine, pravila dostave |
| ordify-geofencing-service | 8083 | zone | Zone dostave + brza provera lokacije (keš) |
| ordify-notification-service | — | — | (planirano, Faza 2) WebSocket/SSE obaveštenja |
| ordify-licence-service | — | — | (planirano, Faza 2) licenciranje instanci, subscription paketi |
| ordify-infrastructure | — | — | Docker Compose, Keycloak, monitoring |
Svi servisi: Quarkus 3.36.x, Java 21, PostgreSQL, Hibernate Panache, REST Jackson.
2. Strategija baza podataka¶
- Database-per-service: svaki servis ima svoju logičku bazu —
meni,porudzbine,zone(+keycloakza Keycloak). Servisi komuniciraju isključivo preko API-ja, nikad preko deljene šeme (već tako radimo: order koristi batch REST ka menu-service). - Dev (Faza 1): jedan PostgreSQL kontejner (
postgres:18.4) sa 4 baze, kreirane krozdb/init/01-create-databases.sql. - Prod (Faza 2+): PG instanca po servisu (K8s StatefulSet), nezavisni backup, nezavisno skaliranje. Aplikacija ne primećuje razliku — menja se samo JDBC URL.
- Skaliranje (redosled poteza kad pojedinačna instanca poraste): kvalitetni indeksi → particionisanje/arhiviranje starih porudžbina → read replike (meni se čita ≫ piše) → PgBouncer (više podova) → sharding po tenantu. Bez sharding-a sada — on-prem model već deli podatke po klijentima (svaka instanca = prirodni shard); milijarda redova u jednoj bazi nije cilj dizajna.
- Redis: ne koristi se kao keš. geofencing-service ima sopstveni in-memory keš (nanosekunde, bez mrežnog hopa); meni tabela je mala; order mora biti konzistentan. Redis (pub/sub) ulazi tek sa notification-service-om i multi-pod deploymentom (signal nove porudžbine svim podovima; alternativa: PostgreSQL LISTEN/NOTIFY).
3. Autentikacija — Keycloak ✅ (Faza 1)¶
Realm ordify — jedan ulaz za celu platformu (on-prem: svaka klijentska instanca ima svoj Keycloak sa svojim realm-om).
Klijenti:
| Klijent | Tip | Namena |
|---|---|---|
ordify-backend |
confidential | Servisni pozivi (service account), kasnije server-to-server |
ordify-admin |
public | Admin panel (web, Faza 3) |
ordify-kitchen |
public | Kuhinjski ekran (web, Faza 3) |
ordify-customer |
public | Sajt kupca (web, Faza 3) |
Role (realm-level): ADMIN, KITCHEN, CUSTOMER — dolaze u JWT kroz realm_access.roles; servisi ih proveravaju na endpoint-ima.
Dev korisnici: admin@ordify.dev (ADMIN), kitchen@ordify.dev (KITCHEN), customer@ordify.dev (CUSTOMER) — usernames: admin, kitchen, customer.
Realm import: keycloak/realm/ordify-realm.json (eksport iz verzije 26.7; format se menja između major verzija — eksportovati iz iste verzije u koju se importuje). Dev: mount + --import-realm. Prod: realm ugrađen u sliku pri build-u.
Zaštita endpoint-a (implementirano):
- menu: POST/PUT/DELETE → ADMIN; GET-ovi i /api/products/batch javni (meni vidljiv bez prijave; batch je interni poziv order-service-a)
- order: PUT /api/orders/{id}/status i GET /api/orders → KITCHEN/ADMIN; POST /api/orders javan (kupac poručuje bez prijave, do Faze 3)
- geofencing: /api/admin/zones (CRUD) → ADMIN; /api/internal/zones/check javan (interno, poziva ga order-service)
Interni pozivi (order→menu, order→geofencing): za sada bez tokena — zaštita na nivou mreže (Docker interna mreža). Tokeni servis-po-servis dolaze sa servisnom diskoverijom (Faza 2).
Tehnologija: quarkus-oidc (application-type=service — bearer-only, verifikacija JWT potpisa lokalno preko JWKS; bez mrežnog poziva po zahtevu) + @RolesAllowed. Konfiguracija kroz env var-ove: OIDC_URL (auth-server-url, default http://localhost:8080/realms/ordify) i POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_PORT (datasource na infra postgres, baze meni/porudzbine/zone).
4. ordify-menu-service¶
Model: Product¶
| Polje | Tip | Napomena |
|---|---|---|
id |
Long | Panache auto |
name |
String | Obavezan (validacija 422) |
description |
String | |
price |
BigDecimal | Cena |
isAvailable |
boolean | Vidljivo kupcima |
isArchived |
boolean | Soft delete |
category |
String | Za sada tekst; plan: zaseban entitet |
API¶
| Metod | Putanja | Opis |
|---|---|---|
| GET | /api/products |
Svi ne-arhivirani proizvodi |
| GET | /api/products/active |
Samo dostupni kupcima |
| POST | /api/products/batch |
Proizvodi po listi ID-jeva (za order-service) |
| POST | /api/products |
Kreiranje (validacija imena) |
| PUT | /api/products/{id} |
Izmena (404 za arhivirane) |
| DELETE | /api/products/{id} |
Meko brisanje → isArchived=true |
Ključne odluke¶
- Soft delete: brisanje samo postavlja
isArchived; istorija porudžbina ostaje netaknuta. - Batch endpoint: omogućava order-service-u da povuče N proizvoda u jednom pozivu.
- Testovi: 15 integracionih testova sa REST Assured (redosled kroz
@Order).
5. ordify-order-service¶
Centralni biznis servis. Prima porudžbine, proverava zonu dostave, kontaktira menu-service za cene.
Modeli¶
Order
| Polje | Tip | Napomena |
|-------|-----|----------|
| customerName | String | |
| deliveryAddress | String | Prikaz na računu |
| latitude / longitude | Double | GPS lokacija kupca |
| note | String (500) | Napomena kupca |
| totalPrice | BigDecimal | Računa se na osnovu trenutnih cena |
| status | OrderStatus | Enumeracija |
| createdAt | LocalDateTime | |
| items | List\<OrderItem> | Cascade ALL |
OrderItem
| Polje | Tip | Napomena |
|-------|-----|----------|
| productId | Long | Link ka proizvodu |
| productName | String | Snapshot u trenutku porudžbine |
| pricePaid | BigDecimal | Snapshot cene |
| quantity | int | |
| order | Order | ManyToOne (JsonIgnore) |
OrderStatus: KREIRANO → PRIHVAĆENO → U_DOSTAVI → DOSTAVLJENO; alternativno ODBIJENO.
API¶
| Metod | Putanja | Opis |
|---|---|---|
| POST | /api/orders |
Kreiranje porudžbine (geofencing + provera dostupnosti) |
| PUT | /api/orders/{id}/status |
Promena statusa (string → enum) |
| GET | /api/orders |
Sve porudžbine |
Tok kreiranja porudžbine¶
- Validacija GPS koordinata (400 ako nedostaju)
- Geofencing provera — REST poziv ka geofencing-service (
/api/internal/zones/check) - Batch poziv ka menu-service (
POST /api/products/batch) za cene - Provera dostupnosti svakog proizvoda (400 ako je nedostupan)
- Snapshot imena/cena u
OrderItem, računanjetotalPrice - Perzistencija sa statusom
KREIRANO
REST klijenti¶
MenuServiceClient(configKey = "menu-api") →POST /api/products/batchGeofencingClient(configKey = "geofencing-api") →GET /api/internal/zones/check?lat=&lng=
Konfiguracija URL-ova: quarkus.rest-client.menu-api.url (5006) i quarkus.rest-client.geofencing-api.url (8083).
6. ordify-geofencing-service¶
Samostalni servis za zone dostave. Ključna prednost: in-memory keš — provere lokacije nikada ne idu u bazu.
Modeli¶
DeliveryZone—name,isActive,coordinates(List\<ZonePoint>, EAGER,@OrderBy(sequence))ZonePoint—lat,lng,sequence,zone(ManyToOne, JsonIgnore)CachedZone—name,polygon(List\<LngLat>) — keš reprezentacijaLngLat—lng,lat
API¶
| Metod | Putanja | Opis |
|---|---|---|
| GET | /api/admin/zones |
Sve zone |
| POST | /api/admin/zones |
Kreiranje (dodeljuje sequence tačkama) |
| PUT | /api/admin/zones/{id} |
Izmena (preslikavanje postojećih tačaka po ID) |
| DELETE | /api/admin/zones/{id} |
Brisanje (cascade + orphanRemoval) |
| GET | /api/internal/zones/check?lat=&lng= |
Provera lokacije → {"isInside": true/false} |
Keš mehanizam¶
zoneCache(volatile) +isDirtyflag- Double-checked locking u
ensureCacheIsFresh() - Invalidacija: CDI event
ZoneChangedEvent(fire posle svake CRUD operacije), osluškivan sa@Observes(during = AFTER_SUCCESS)→isDirty = true - Provera:
zoneCache.parallelStream().anyMatch(...)— paralelna provera kroz sve zone
GeofencingUtils¶
sortZone(DeliveryZone)— sortiranje temena poligona po uglu oko centra (centroid) — obezbeđuje ispravan redosled tačakaisPointInPolygon(LngLat, List<LngLat>)— ray-casting algoritam
Testovi¶
GeofencingUtilsTest — tačka u poligonu (uspeh/propadanje) i sortiranje zone.
7. ordify-notification-service (planirano, Faza 2)¶
- WebSocket/SSE veze sa frontendom (kuhinjski ekran)
- Jedini zadatak: progurati obaveštenje o novoj porudžbini u milisekundi
- Integracija: order-service šalje signal nakon kreiranja porudžbine
- Multi-pod (K8s): signal se prosleđuje svim podovima preko Redis pub/sub (ili PG LISTEN/NOTIFY)
8. ordify-infrastructure (Faza 1, u izradi)¶
Centralni repo za:
- docker-compose.yml + docker-compose.dev.yml (+ docker-compose.prod.yml nacrt) — PostgreSQL 18.4 + Keycloak 26.7
- db/init/ — skripte za kreiranje baza
- keycloak/realm/ — realm eksport ordify-realm.json
- monitoring/ — Prometheus v3.13.2 + Grafana 13.1 + alerting (Discord webhook) ✅
- CI/CD — GitHub Actions: build + test + native slike → GHCR ✅
9. Sigurnost i produkcija (TODO)¶
- [x] Odluka: Keycloak 26.7, realm
ordify, klijenti/role definisani - [x] OIDC zaštita servisa (
quarkus-oidc+@RolesAllowed) — menu, order, geofencing - [x] Datasource na infra baze (
meni/porudzbine/zone) krozPOSTGRES_*env var-ove - [ ]
quarkus.hibernate-orm.database.generation→validate/nonevan DEV-a (trenutnodrop-and-create) - [ ] Servisna diskoverija umesto hardkodovanih
localhostURL-ova (Faza 2) - [ ] HTTPS (prod: reverse proxy / K8s ingress)
- [ ] Backups strategija (prod, Faza 2)