Marketplace · Multi-vendor food marketplace — Germany / EU

PlazMarket: a German marketplace where the law is part of the domain model

A multi-vendor food marketplace for the German market, in production: five locales (two right-to-left), 15 bounded contexts, multi-seller settlement and German-compliant invoicing — on .NET 10, Next.js and PostgreSQL.

Client
PlazMarket
Industry
Multi-vendor food marketplace — Germany / EU
Year
2025
Status
Live
  • Discovery and analysis
  • UX and UI design
  • Software architecture
  • Front-end development
  • Back-end and API development
  • Database design
  • Third-party integrations
  • Deployment and DevOps
  • Technical SEO
  • QA and testing
  • Support and ongoing development

The result, in numbers

15
bounded contexts, isolated stores

238 endpoints across 19 modules

845
automated tests

unit and integration, against a real database

5
languages

two of them right-to-left

The challenge

The hard part of a German marketplace is not the shopping cart. A German price label has to answer questions an ordinary e-commerce schema never asks.

What does this cost per kilogram — based on the price the shopper can actually get today, not the list price? How much of the total is a refundable container deposit, which is not part of the product's price at all? If it was on offer last week, what was the lowest price in the thirty days before? Get any of these wrong and the number on the page is not merely untidy — it is a misstatement of price.

The same is true after the sale. An invoice must carry a gapless number and mandatory fields, and if anything is refunded, a correction document must repeat those particulars. Money is split across sellers, held in reserve against returns, and paid out — and every step has to produce entries a tax audit can follow and that no code may quietly rewrite.

On top of all of it, the site has to work in five languages, two of which — Persian and Arabic — are written right to left.

The solution

Instead of bolting the law onto the system, it was written into the domain model. The unit price is computed from the price actually payable, so a campaign carries the per-kilo figure with it. The deposit is a domain value with its own VAT treatment, kept separate from the cart quote through the invoice line to the refund. Price history is recorded per offer so the thirty-day figure reads correctly. The return window comes from a policy tier snapshotted onto the order at the moment of sale, so a later policy change cannot alter what was promised.

Money is Money throughout rather than decimal, with one rounding rule applied once per line; and totals are carried forward from the quote into the order, the invoice and the settlement instead of being recomputed at each step.

Three products were built on one platform: a storefront with faceted search on Elasticsearch and multi-seller orders split into independent parcels; a seller panel with KYB onboarding, bulk CSV import, campaigns, fulfilment and returns, commission, reserve holds and payout statements; and an operator panel with catalog governance, an invoice approval queue, VAT by rate and payout execution.

GDPR and DSA compliance is structural in the same way: a subject-access pipeline fans out over every context with an email-verified identity check and an SLA clock; seller legal identity is public on the shop page; and an abuse report is routed to a moderator rather than triggering an automatic takedown.

Key features

  • Multi-seller orders

    One cart, several sellers; split into independent parcels with separate fulfilment and returns.

  • Settlement and reserve holds

    Commission, holds until the return window closes, debits and payout statements — on a ledger that refuses edits.

  • Gapless invoicing

    Per-issuer sequential numbering under a row lock, with statutory correction documents.

  • Faceted search

    Brand, category, origin, organic and price on Elasticsearch, with variant families collapsed to one card.

  • Seller KYB

    Legal identity, registration, VAT ID, bank details and document review before trading.

  • Five locales with real locale behaviour

    German, English, Turkish, Persian and Arabic; Jalali and Hijri dates from the browser’s own locale data, not hand-written special cases.

  • GDPR and DSA compliance

    A subject-access pipeline across every context, public trader identity, and human notice-and-action handling.

  • 19 email templates in five languages

    HTML and plain text with the recipient’s direction, from a domain authenticated with SPF, DKIM and DMARC.

Architecture and stack

Style
Modular monolith
Patterns
Clean architectureDomain-driven designEvent-drivenOutbox patternCQRS

Clean Architecture across five projects — Domain, Application, Infrastructure, Api and a shared kernel — with 15 bounded contexts, each owning its schema and its DbContext. Nothing reaches across a context boundary with a join; contexts talk through domain events delivered by a transactional outbox, so a failure in one does not roll back another and no event is lost.

The reason for that discipline is money. Anything that decides money — coupon redemption, seller payouts, gapless invoice numbering — takes a row lock inside an explicit transaction, and each is covered by concurrency tests that run real parallel requests against a real database; these defects are visible nowhere else, since an in-memory fake serialises by construction. The settlement ledger rejects UPDATE and DELETE at the database level, and corrections are compensating entries.

One more rule held throughout: one definition per rule. Where two surfaces need the same answer — the parcel count, the effective price, a cache key — they call one function, and the invariant is tested directly.

Ten hosted services carry the work that is not request-shaped: an outbox dispatcher, an email dispatcher, expiry sweepers for payments and stock reservations, reserve release, retention enforcement, and a pricing scheduler that starts and ends campaigns.

Back end and API
.NET 10C#MediatREntity Framework CoreREST APIClean Architecture

Warnings as errors, banned-API analyzers, 49 migrations

Database
PostgreSQL

PostgreSQL 17 — source of truth

Cache
Redis

Redis 7 — cache and distributed rate limits

Search
Elasticsearch

Elasticsearch 8.15 — search and facets

Front end
Next.jsReact.jsTypeScript

Storefront: App Router, server components, next-intl across five locales

Front end
Vite

Seller and operator panels: an SPA with TanStack Query and a shared design system

Infrastructure and deployment
DockernginxCloudflare

Docker Compose, nginx proxy, CDN and a mail server with SPF, DKIM and DMARC

Integrations

  • Hosted payment gateway with webhooks, refunds and chargeback handling
  • Carriers for handover and parcel tracking
  • A self-hosted mail server with SPF, DKIM and DMARC

Have a similar project in mind?

Fifteen minutes is enough to say what it needs, how long it takes and where to start.