Architecture
The ShopiTerm software system consists of multiple microservices.
This page lists them and gives some detail on their implementation.
For running and configuring them, see the installation and configuration pages.
External Dependencies
We use two main external dependencies in this software system: NATS and PostgreSQL.
NATS
NATS is a messaging system used by ShopiTerm for event-driven interactions.
It offers a simple-to-use interface to subscribe to and publish messages on so-called subjects.
This allows us to notify other services when something needs to be started or has finished.
PostgreSQL
PostgreSQL is the database management system used by ShopiTerm.
Each service that needs persistence in the form of a database uses it.
We use the diesel library for interfacing with PostgreSQL.
This gives us strong compile-time guarantees about the correctness of our queries.
All services use either an asynchronous variant of diesel or a connection pool, ensuring fast handling of concurrent requests to our system.
Where necessary (e.g. when updating stock information) we use transactions to ensure that operations consisting of multiple database operations result in a consistent state.
Catalogue
The catalogue service keeps track of the items available within the store. It does not track actual stock information but rather the metadata about a given item. An item has the attributes one would expect from a shop such as name, description and price. Additionally, the catalogue offers the ability to add arbitrary tags to an item.
Warehouse
The warehouse component manages the actual stock of items for ShopiTerm.
There can be any number of warehouses, each with their own stock of items.
Warehouses can be created and deleted and items can be added or removed from them.
In order to support the ordering process, items can be reserved at a warehouse before an order is finalized.
This prevents a single item instance to be sold multiple times.
Orders
The order service handles checkout and keeps the canonical record of placed orders.
It accepts a CreateOrder payload, validates the shipping address and ordered items, prices the items, reserves the required stock in the warehouse service and stores the order together with its items and shipping address in PostgreSQL.
Order creation is transactional: if persisting the order fails, any reservations are rolled back. Once the order has been written successfully, the service publishes the placed order on NATS so that the payment service can create a payment flow for it. The service also exposes endpoints to fetch an order, its items and payment information, and to mark an order as shipped. It also publishes an event to NATS when the order is marked as shipped for the notification service to notify the client that the command is shipped.
Notificator
The notification service sends email confirmations to customers at key stages of the order lifecycle. It listens to NATS events published by the payment and orders services, fetches the corresponding order details from the order service, and sends customer emails via SMTP. The service sends three types of notifications: order confirmation emails when a payment is created, payment confirmation emails when payment is marked as paid, and shipping confirmation emails when an order is marked as shipped.
Payment
The payment service listens for newly created orders via NATS and creates a Taler payment for them.
Taler returns a payment URI that is announced on NATS and can then be displayed to customers for payment with their Taler wallet.
All currently open payments are tracked using a NATS key-value store.
Once a payment is marked as paid by Taler, the corresponding ShopiTerm order is announced to be paid via NATS.
Recommendations
The recommendation service generates product suggestions for ShopiTerm customers.
It exposes three strategies over HTTP: globally popular items, items similar to a given product, and personalized picks for the current user.
A local mirror of the catalogue’s product metadata is kept up to date via NATS events from the catalogue, and the popular and similar rankings are pre-computed on a schedule so read paths are cheap.
Similarity is exact-match Jaccard on (key, value) tag pairs — there is no NLP and no embeddings, so the quality of the similar lists depends directly on the catalogue’s tag taxonomy.
Personalized recommendations mix two signals over a recent-interactions window: items similar to those the user has recently engaged with, and globally popular items the user has not yet seen.
Interactions (view, add_to_cart, purchase) are weighted differently when scoring popularity, with purchases counting the heaviest.
The TUI client consumes these endpoints in two places: a Similar items panel inside the item detail view, and a You might also like block on the order confirmation screen.
TUI Client
The user interface to ShopiTerm is built using ratatui.
It is exported as a library to be used by the SSH server.
The interface consists of multiple views, one for each task such as catalogue browsing or doing the payment.
A loop-based renderer then uses these views to assemble the user interface.
Keyboard input is handled based on the currently active view.
The TUI is implemented as a state machine with a View enum that tracks which screen the user is on.
Views include the item list, search, cart, checkout, payment (with QR code display), order confirmation, and a full admin panel for managing items, warehouses, and orders.
Keyboard input is dispatched based on the active view, and the renderer redraws the current view on each event.
The client library also integrates with the recommendations service, displaying similar items in the item detail view and personalized suggestions on the order confirmation screen.
Only items with available stock are shown to customers, and cart quantities are capped at the available stock level.
SSH Server
The SSH server is the main entry point for users.
It accepts SSH connections using public-key authentication and hosts the TUI client for each connected session.
When a user connects, their SSH key fingerprint is registered in a PostgreSQL database.
Admin users are identified by matching against a configured admin public key.
Upon session creation, the server generates a JWT token (HS256) containing the user ID and role, which is used for authenticated API requests through the gateway.
The server manages terminal state via russh, translating raw SSH input bytes into keyboard events and piping ratatui output back over the SSH channel.
It also spawns a background payment poller to provide live payment status updates within the TUI.
Sessions have a one-hour inactivity timeout.
Common Library
The shopiterm_common is a Rust library helping us in building the whole project.
It is mainly a collection of data structures that are exchanged between services.
These structures are set up for serialization and deserialization, ensuring easy re-use between services.
API Gateway
The API gateway uses KrakenD to route HTTP traffic from clients to the backend microservices.
It listens on port 8080 and exposes a unified API surface covering the catalogue, orders, warehouse, and recommendations services.
Mutating endpoints (create, update, delete) are protected by JWT validation (HS256, issuer shopiterm).
The gateway also injects the X-User-Id header from the JWT sub claim for personalized recommendation endpoints.
Read-only endpoints are publicly accessible without authentication.
API Client
The API client is not an actual service but rather a library used by the services to exchange information. It provides the ability to make authenticated requests to the gateway and fetch all the different data structures used by the different services.