Bird's Eye Overview

This page explains PackyTrace's target architecture from a technological point of view: what each service owns, how requests travel through the system, and how services communicate without sharing their private data.

Some flows described here belong to later phases and are not implemented yet. The phase-specific simplified-description pages explain what currently exists.

The Whole System

                             Public HTTP
Web App ───────────────────> API Gateway
                                  |
                    Internal HTTP reverse proxy
              ┌───────────┬───────┴────────┬──────────────┐
              v           v                v              v
          Passport     Identity     Personalization    Fridge
           Service      Service         Service         Service
              |           |                |              |
              v           v                v              v
          passport     identity      personalization    fridge
           schema       schema           schema          schema
              \           \                /              /
               └───────────── PostgreSQL ────────────────┘

Services ───────────────> Kafka topics ───────────────> Services
                              |
                              v
                     Measurement Pipeline
                              |
                     aggregated events only
                              v
                     Brand Analytics Service

Identity Service ───── HTTP ─────> Keycloak
API Gateway ────────── JWKS ─────> Keycloak

All services are independently deployed applications. During local development, Docker Compose starts and connects them.

Two Communication Styles

PackyTrace services communicate in two different ways.

Synchronous Internal HTTP

Use HTTP when the caller needs an immediate answer before it can continue:

Caller sends request
        |
        v
Receiving service processes it
        |
        v
Receiving service returns response
        |
        v
Caller continues

For example, Personalization must confirm consent before storing a Health Profile:

Personalization Service
    ── "Has account 123 granted consent?" ──> Identity Service
    <──────────────────── "Yes" ─────────────

If Identity Service is unavailable, Personalization cannot safely continue.

Asynchronous Kafka Events

Use Kafka when a service needs to announce a fact that has already happened:

Producing service
        |
        v
Publishes event to Kafka
        |
        v
Interested services process it later

For example:

Passport Service ── ProductScanned ──> Kafka ──> Measurement Pipeline

Passport does not need to wait for Measurement Pipeline before returning the scan result.

API Gateway: The Public Front Door

The API gateway is the only public backend entry point:

Web App ──> API Gateway ──> Owning Service

It is responsible for:

  • routing requests to the correct service;
  • validating Keycloak-issued JWTs;
  • converting a verified JWT identity into a trusted X-Account-Id;
  • handling pseudonymous Visitor IDs;
  • allowing approved browser origins through CORS;
  • eventually deriving and enforcing trusted Brand scope.

For an authenticated request:

GET /api/v1/me/profile
Authorization: Bearer <keycloak-jwt>

the gateway:

  1. verifies the JWT using Keycloak's JWKS public keys;
  2. checks its signature, issuer, and expiry;
  3. reads the account ID from the JWT's sub claim;
  4. removes any caller-provided X-Account-Id;
  5. injects the verified account ID;
  6. proxies the request to the owning service.

Backend services trust the gateway-injected identity, not identity claims sent directly by the browser.

Keycloak and Identity Service

Keycloak and Identity Service both deal with identity, but they own different parts.

Keycloak Owns Authentication

Email + password ──> Keycloak ──> signed tokens

Keycloak owns:

  • credentials and password security;
  • login sessions;
  • access and refresh tokens;
  • authentication roles.

Identity Service Owns Business Identity

Identity Service owns:

  • PackyTrace accounts;
  • anonymous visitor-to-account links;
  • consent history;
  • Brands;
  • Brand users.

During registration:

Web App
  ──> API Gateway
  ──> Identity Service
  ──> Keycloak Admin API: create authentication user
  <── Keycloak user ID
  ──> PostgreSQL: create PackyTrace account
  ──> PostgreSQL: link existing Visitor ID
  ──> Kafka: publish VisitorLinkedToAccount

The Keycloak user ID and PackyTrace account ID are identical, giving every service one consistent account identifier.

Passport Service

Passport Service owns products, product passports, and scans:

Web App ──> Gateway ──> Passport Service ──> passport schema

During a scan it:

  1. parses and validates the GS1 product identifier;
  2. queries its product catalog;
  3. stores the scan record;
  4. requests a personalized verdict when appropriate;
  5. returns the resolved passport;
  6. publishes ProductScanned to Kafka.

Kafka publishing is currently fire-and-forget, so Kafka downtime does not make the user's scan fail.

Personalization Service

Personalization Service owns:

  • Health Profiles;
  • versioned verdict rules;
  • personalized verdict calculations.

Before storing a Health Profile:

Personalization Service
  ── internal HTTP ──> Identity Service consent endpoint
  <── consent state ──

During an authenticated scan:

Passport Service
  ── internal HTTP ──> Personalization Service
  <── personalized verdict ──

If Personalization is unavailable, Passport should still return the product passport without a personalized verdict.

Personalization must never read Identity Service's database directly.

Fridge Service

Fridge Service will own fridge items and their append-only history.

To add an item:

Web App
  ──> API Gateway
  ──> Fridge Service
  ── internal HTTP ──> Passport Service
  <── trusted product-and-scan snapshot
  ──> fridge schema
  ──> Kafka: ItemAddedToFridge

Passport verifies that the authenticated account owns the scan before returning the item snapshot.

Fridge history contains events such as:

ItemAdded
ItemConsumed
ItemDiscarded

The service calculates current fridge state by folding those historical events.

Measurement Pipeline

Measurement Pipeline consumes operational facts from Kafka:

Passport facts ────────┐
Identity facts ────────┼──> Kafka ──> Measurement Pipeline
Fridge facts ──────────┘

Its job is to:

  1. receive individual operational facts;
  2. group them into minimum-sized aggregates;
  3. remove individual-level details;
  4. publish only privacy-safe Brand metric batches.
Measurement Pipeline
  ── BrandMetricBatchPublished ──> Kafka
  ───────────────────────────────> Brand Analytics Service

This is the privacy wall between consumer activity and Brand analytics.

Brand Analytics Service

Brand Analytics receives and stores only privacy-safe aggregate metrics:

Brand Analytics Service
  <── Kafka
  <── BrandMetricBatchPublished

It must never receive:

  • individual scans;
  • Visitor IDs;
  • Account IDs;
  • Health Profiles;
  • personalized verdicts.

Future Brand-facing queries will travel through the gateway:

Brand User
  ──> Gateway validates JWT and derives trusted Brand scope
  ──> Brand Analytics Service
  ──> brand_analytics schema

The Brand scope must come from trusted identity data, never from a brand_id chosen by the caller.

Database Isolation

PackyTrace uses one PostgreSQL server with a separate schema and restricted role for each data-owning service:

PostgreSQL
├── passport
├── identity
├── personalization
├── fridge
├── measurement
└── brand_analytics

For example:

passport-service ── passport_svc role ──> passport schema only
identity-service ── identity_svc role ──> identity schema only

A service cannot query another service's schema. When it needs another service's data, it must request it through internal HTTP or react to a Kafka event.

Complete Target Scan Flow

The main target request flow is:

Web App
  |
  | POST /api/v1/scans
  | Authorization: Bearer JWT
  v
API Gateway
  |
  | verifies JWT using Keycloak JWKS
  | injects X-Account-Id
  v
Passport Service
  |
  | queries product catalog
  | stores scan record
  |
  | internal HTTP request
  v
Personalization Service
  |
  | calculates verdict from Health Profile
  v
Passport Service
  |
  | publishes ProductScanned to Kafka
  | returns passport + verdict
  v
Web App

Separately, consumer facts become privacy-safe Brand analytics:

Kafka
  ──> Measurement Pipeline
  ──> privacy-safe aggregates
  ──> Kafka
  ──> Brand Analytics Service

The Core Technological Rule

A service owns its code and database schema. Immediate questions use internal HTTP. Announcements about completed facts use Kafka. Public requests always enter through the API gateway. Authentication belongs to Keycloak. Business identity belongs to Identity Service.