Skip to main content

Containers

The system is a modular monolith plus three neighboring processes: the Next.js application, the background worker, and PostgreSQL. Docusaurus publishes this documentation and is not on the request path for bookings.

Express and the worker reach PostgreSQL only through the generated Prisma Client. Both use DATABASE_URL and no other database connection string.

Responsibilities​

ContainerOwnsDoes not own
Next.jsPages, forms, and presentation of states returned by the API.Prices, payment truth, gallery authorization, reward balances.
ExpressBusiness rules and all writes that change booking, payment, access, or reward state.Image bytes. It stores keys only.
WorkerJobs that can be retried: mail, thumbnails, provider refunds, payment reconciliation, reward expiry.A second copy of the business rules. It calls the same modules or performs the job payload the API already committed.
PostgreSQLDurable records, constraints, and the ledger.Files and secrets.
Object storageOriginals and thumbnails in a private bucket.Who is allowed to see a file.
DocusaurusThe design and the OpenAPI document.Live customer data.

Why these are separate​

Next.js must not hold the PostgreSQL administrator credential or the Stripe secret. A compromise of the public site should not be a compromise of the ledger. The worker must not expose an HTTP port. Webhooks enter only through Express, which verifies signatures before a job is trusted.

Express modules share one database and one deployment. That is the modular monolith. The process boundary around Next.js, the worker, and PostgreSQL is there so each can be placed on the host that ScalaHosting actually supports. Placement is decided in deployment, after that check.

Outbox rule​

State changes and the jobs they require are committed together. Rejecting a booking writes the booking status, the order status, the audit row, and a refund job in one transaction. The worker then calls Stripe. The provider is not called inside the open database transaction. If the process crashes, the job remains available and is retried. Provider calls use the payment record's idempotency so a retry does not refund twice.