All Projects

Terungal — Senegalese food marketplace

A personal project with professional ambitions: a multi-restaurant marketplace for Senegalese dishes, designed as microservices (18 Spring Boot services, 4 BFFs, 3 Angular apps) and built with Claude Code.

Terungal — Senegalese food marketplace
JavaSpring BootAngularTypeScriptTailwind CSSPostgreSQLKafkaDockerStripeMinIOValkeyCaddyOpenTelemetryGrafanaFlutterTestcontainersClaude Code

Terungal is an ordering platform for Senegalese dishes — thieboudienne, yassa, maafe… — cooked to order and delivered. It is a personal project that I am building with the ambition of turning it into a professional product: it started as a single shop and became a multi-restaurant marketplace, ready to onboard restaurant owners and bill them a subscription.

This page tells the story in three parts: its design (domain split and microservices architecture), its development (code structure, security, quality and delivery) and its presentation, application by application.

Terungal storefront home page
Figure 1 — Terungal storefront home page
  • 18 Spring Boot microservices (Java 21), each with its own PostgreSQL database

  • 4 BFFs and 3 Angular 22 applications — storefront, back office, console — plus a Flutter mobile app in progress

  • About 590 unit and integration tests, 80 architecture decisions recorded as ADRs

  • Designed and built with Claude Code as the development agent

Design

Starting point: a monolith that had to evolve

The project grew out of a first Spring Boot monolith: a single jar redeployed on every change, a shared data model, a failing checkout, six identified security flaws and a single automated test. Rather than patching it, I chose to redesign the platform so it could grow: several restaurants, subscriptions, an admin console and, eventually, a mobile app.

Splitting the domain (Domain-Driven Design)

The first step was a full inventory of the monolith, then a split into bounded contexts. Each context becomes an autonomous service that owns its data. They are ranked by value: the core domain gets the most care, while generic contexts rely on proven solutions.

The platform is multi-tenant: the tenant is the organisation, the scope is the restaurant. Every record and event of the business contexts carries the organisation and restaurant identifiers.

Target architecture

Applications only talk to their BFF (Backend for Frontend), the single entry point exposed behind Caddy. Services are private, share no database and collaborate through Kafka events.

  • One database per service: no cross-service joins, versioned Flyway migrations.

  • No direct calls between services: collaboration only goes through events.

  • One BFF per application: each front-end gets an API shaped for its screens and never sees the tokens.

Reliable events: outbox and idempotency

An event must never be lost or applied twice. Each service writes the aggregate and the message in the same transaction (transactional outbox); a relay then publishes them in order. On the consumer side, any message already processed is ignored.

Ordering as a saga

The checkout, broken in the monolith, was rebuilt as a saga orchestrated by the order service: payment, stock reservation and cart clean-up are chained through events, with compensations (cancellation, refund) when something fails.

Migrating without stopping

The migration follows the strangler pattern: the BFF routes each route either to the monolith or to the already migrated service, and the monolith's data is replicated to the services (Debezium and Kafka). Contexts were extracted in waves, from the least risky to the most central.

Every structural choice — data per service, messaging, saga, security, multi-tenancy, deployment — is recorded in an ADR (Architecture Decision Record): 80 decisions documented so far.

Development

One service, three modules: hexagonal architecture

Every microservice follows the same structure, inspired by hexagonal architecture and Clean Architecture: three Maven modules domain, application and infrastructure. Dependencies always point towards the domain, written in plain Java with no framework.

  • Rules checked automatically (ArchUnit): a controller only calls inbound ports, the domain depends on nothing.

  • Shared conventions: naming, RFC 9457 REST errors, an idempotency key on sensitive creations.

  • Shared modules for messaging (versioned event contracts, outbox, idempotent consumers).

Security

Identity is centralised in identity-service (OAuth2 / OIDC). Tokens never leave the BFFs: the browser only gets a session cookie, and every service re-checks the JWT and the rights on the requested resource.

  • Role- and scope-based access control (organisation, restaurant).

  • A chained audit log: every sensitive action is sealed with the hash of the previous one.

  • Versioned legal documents: the accepted version is stored with each order.

Front-ends

Three Angular 22 applications share a common library: the customers' storefront, the restaurants' back office and the platform console. State is managed with signals (NgRx Signals) and the UI is built with Tailwind CSS 4. A Flutter mobile app, with its own BFF, is in progress.

Quality, delivery and operations

  • Tests: about 590 unit and integration tests (Testcontainers) and 76 characterisation tests of the monolith.

  • Delivery: one Docker image and one pipeline per application; each service is deployed independently.

  • Environments: a lightweight setup (infrastructure in Docker) or a full one through Docker Compose profiles.

  • Observability: end-to-end traces (OpenTelemetry), correlated JSON logs, business metrics and alerts in Grafana.

Built with Claude Code

Terungal was designed and built with Claude Code as its development agent, from the inventory of the monolith to the Angular applications. Each migration step is driven by a prompt versioned in the repository, conventions live in CLAUDE.md files and every structural choice has its ADR. Generated code is reviewed, tested and validated before merging.

Presentation

The storefront

The storefront takes customers from choosing a dish to placing an order, and adapts to them once signed in.

Collections and the signed-in customer's “usual dishes”
Figure 10 — Collections and the signed-in customer's “usual dishes”
Catalogue: most-ordered dishes, ratings, prices and monthly orders
Figure 11 — Catalogue: most-ordered dishes, ratings, prices and monthly orders
Dish page: rating, reviews and favourites
Figure 12 — Dish page: rating, reviews and favourites
Dish page: order panel — nothing is charged before payment is confirmed
Figure 13 — Dish page: order panel — nothing is charged before payment is confirmed
Recipe published by the restaurant: preparation, cooking, servings, difficulty
Figure 14 — Recipe published by the restaurant: preparation, cooking, servings, difficulty
Marketplace: nearby restaurants open now
Figure 15 — Marketplace: nearby restaurants open now
Customer area: favourites across all restaurants
Figure 16 — Customer area: favourites across all restaurants
Customer area: dietary preferences (diets and allergens)
Figure 17 — Customer area: dietary preferences (diets and allergens)

The restaurant back office

Each restaurant runs its business from its back office: today's service, catalogue, offers, opening hours and its shop window.

Back office: today's activity and stock status
Figure 18 — Back office: today's activity and stock status
Back office: dish catalogue, prices and stock
Figure 19 — Back office: dish catalogue, prices and stock
Back office: offers, discounts and promo codes
Figure 20 — Back office: offers, discounts and promo codes
Back office: opening hours, public holidays and closures
Figure 21 — Back office: opening hours, public holidays and closures
Back office: collections shown in the shop
Figure 22 — Back office: collections shown in the shop
Back office: home page configuration with live preview
Figure 23 — Back office: home page configuration with live preview

The platform console

The console, reserved for administrators, runs the marketplace as a whole: activity, restaurant onboarding, business model, compliance and traceability.

Console: dashboard — gross volume, commissions, recurring revenue
Figure 24 — Console: dashboard — gross volume, commissions, recurring revenue
Console: versioned subscription plans
Figure 25 — Console: versioned subscription plans
Console: the platform's versioned legal documents
Figure 26 — Console: the platform's versioned legal documents
Console: reference data shared by all restaurants
Figure 27 — Console: reference data shared by all restaurants
Console: chained, verifiable audit log
Figure 28 — Console: chained, verifiable audit log

Outcomes and next steps

  • From monolith to platform: one deployable application became 18 services, 4 BFFs and 3 front-ends, each delivered independently.

  • Reliable ordering: checkout runs through an order, payment and stock saga.

  • Security: the monolith's six flaws are fixed and verified.

  • New capabilities: multi-restaurant support, subscriptions, offers, platform console.

Next steps towards a professional product: going live and shutting down the monolith, real-time delivery tracking and the customer mobile app.