Part V — Case study · Chapter 7

ChatFlow: a middleware brief

~25 min read3 interactive widgets3 plates

In this chapter

  1. The brief and its general objective
  2. General architecture
  3. The five required components
  4. Security constraints and requirements
  5. Expected deliverables
  6. The complete cycle, end to end
  7. Implementation suggestions and optional extensions
  8. Reading the brief through the course lenses
  9. Check your understanding

1. The brief and its general objective

The last document of the course materials is not a lecture but a project brief: Traccia Progetto — ChatFlow Middleware. It is written in the language of a client rather than of an engineer, it is two pages long, and it is exactly the kind of artefact that Chapter 1 described when it introduced the project work option — a set of requirements provided by a commissioner, to be turned into a domain model and a delivery pipeline by the students.

Editor's note

The original brief is in Italian. This chapter reports its content in English, keeping verbatim the strings that are data rather than prose — the command /riepilogo 2025, the tenant name ComuneXYZ, the JSON keys ore_totali and assente, and the reply text — because an identifier that gets translated stops being an identifier. Sections 1 to 7 report the brief; section 8 is explicitly an exercise: it applies the criteria of Chapters 2 to 6 to the brief, and says so wherever it goes beyond what the document states.

General objective

Build a middleware system that:

Four verbs, four responsibilities. Notice that the middleware is defined entirely by what passes through it: it owns no business data of its own, it holds no user accounts, and its value is the translation and the routing. That observation will do a lot of work in section 8.

2. General architecture

The brief gives the architecture as a single line:

[Telegram/WhatsApp] ⇄ [Middleware BridgeBot] ⇄ [Gestionale REST Multi-Tenant]

That is: messaging channelsthe BridgeBot middlewarea multi-tenant REST management system. Both arrows are bidirectional, which is the whole difficulty: the response must find its way back not merely to "the user", but to the correct channel of the original sender.

3. The five required components

#ComponentRequirements
1 Handling input from messaging channels Integration with the Telegram Bot API; integration with the WhatsApp Cloud API (or a simulation of it)
2 Definition and parsing of a meta-language An intermediate language for commands; a parser that produces standardised JSON objects
3 Routing requests to the management backends Each tenant has an API key; requests are forwarded over REST with an Authorization header
4 Returning the response to the user Formatting of the response; delivery on the originating channel
5 Configuration and security Mapping chatId → tenant / API key; safe logging and rate limiting

Component 2 is the one worth pausing on, because the course has a whole chapter about it. An intermediate language for commands with a parser producing standardised JSON objects is, in the vocabulary of Chapter 5, an external DSL: a custom syntax, hence a custom parser, whose output is a model. The choice the brief leaves open — how rich that language should be — is precisely the DSL-versus-GPL trade-off: few and static constructs, a clear domain boundary, a small and reachable user base, fast-paced evolution.

Component 5 is what makes the system multi-tenant in practice. A chatId arrives from a messaging platform and means nothing to the backend; the mapping turns it into a tenant identity and the credential to act on that tenant's behalf. Everything else in the flow depends on that lookup being correct.

4. Security constraints and requirements

Four constraints, stated flatly:

Read as a set, they describe a deliberate narrowing of responsibility. The third constraint is the strongest: by refusing to authenticate end users, the middleware also refuses to hold user credentials, which removes an entire class of risk from the system — and simultaneously shifts weight onto the chatId mapping, since that mapping becomes the only thing deciding on whose behalf a request is made.

Watch out

Two of the four constraints concern logs, not features: nothing sensitive in clear, and debug logging with obfuscated data. Logs are the most frequently forgotten copy of the data — they are written by everybody, read by anybody, and rarely reviewed. The brief pairs them with rate limiting in component 5, which is the other operational concern that only shows up under real traffic.

5. Expected deliverables

DeliverableNote
Documented source codeDocumentation is part of the delivery, not an afterthought
Configuration file (JSON/YAML)The chatId-to-tenant mapping and the rest of the configuration live outside the code
Startup script or DockerfileThe deployment story is an explicit deliverable
Technical READMEThe entry point for whoever inherits the system
Optional extension: a web UI for configurationListed as optional here, and again among the extensions

Two of the five items are about running the thing rather than writing it. That is the same emphasis the course places everywhere: a configuration file separated from the code is what allowed an MLflow Project to be re-run with different parameters in Chapter 6, and a Dockerfile is the deploy automation via containerization that Chapter 1 listed among the exam requirements.

6. The complete cycle, end to end

The brief closes its functional part with one worked example. It is six lines long and it exercises every component listed in section 3.

User (Telegram):  /riepilogo 2025

→ BridgeBot receives the message with chatId: 654321
→ maps chatId to tenant 'ComuneXYZ'
→ retrieves the API key and sends the REST request
→ receives JSON: { 'ore_totali': 124, 'assente': false }
→ replies to the user:
  'Nel 2025 hai totalizzato 124 ore. Nessuna assenza registrata.'

In English, the command means summary 2025, the JSON keys mean total hours and absent, and the reply reads "In 2025 you accumulated 124 hours. No absence recorded." The tenant name ComuneXYZ stands for a municipality.

Follow the responsibilities through the six lines: parsing (a command with one argument becomes a structured request), identification (a channel-level chatId becomes a tenant), authorisation (the tenant's API key goes into the Authorization header), invocation (REST), formatting (a JSON payload becomes a sentence a human wants to read), and delivery (back on Telegram, because that is where the message came from). The response is not the JSON: it is prose generated from the JSON, which is why "formatting the response" is a named component and not an implementation detail.

7. Implementation suggestions and optional extensions

The brief suggests, without prescribing:

  • FastAPI — the HTTP surface (webhooks in, configuration UI if any)
  • httpx — the REST client towards the management backend
  • python-telegram-bot — the Telegram channel integration
  • Express.js — the HTTP surface
  • axios — the REST client
  • node-telegram-bot-api — the Telegram channel integration
  • Optional DB (SQLite) for logs and configuration — note the word optional: the mapping may equally live in the JSON/YAML configuration file listed among the deliverables.
  • Modularity, to divide the work among students — the brief states this as an implementation suggestion, which makes the module decomposition a project-management requirement as much as a technical one.
  • Web UI for tenant management
  • Support for voice messages → transcription
  • Telegram interactive buttons
  • Replies in multiple languages

The last suggestion in the third tab is the one with teeth. Modularity, to divide the work among students is the brief asking for a decomposition whose seams match the team — which is the same argument that Chapter 4 made about contexts, where the boundary is organizational as well as technical, and where the best practice reads one responsible person or team for each context.

8. Reading the brief through the course lenses

Editor's note — this section is an exercise

Everything up to here reported the brief. What follows applies the criteria taught in Chapters 1 to 6 to that text. The brief itself does not mention DDD, DSLs, hexagonal architecture or CI/CD: the mapping below is the work the students are expected to do, and it is presented as one defensible reading, not as the answer.

Which words belong to the ubiquitous language?

The brief hands over a vocabulary with almost no noise: message, channel, chatId, meta-command, tenant, API key, request, response. Following Chapter 2, these are the words that should appear in the code, and the workflow prescribes recording their meaning in a glossary rather than assuming it. Two of them repay the discipline immediately:

Component 5 of the brief exists precisely because those two words live in different contexts, and the mapping is the point of contact between them — the sort of junction a context map is meant to record.

Which building blocks would the concepts become?

Applying the decision procedure of Chapter 3 — identity or interchangeability, thing or capability — gives a plausible first classification:

Concept from the briefCandidate blockWhy
ChatId, ApiKey, TenantIdValue objectsThey are their content: two chat ids with the same value are the same chat id. Immutability also helps with the "nothing sensitive in clear" constraint, since a value object is easy to wrap in a type that controls its own rendering
TenantEntityIt has an identity that survives changes to its attributes (name, key rotation)
MetaCommand (the parsed request)Value objectA standardised JSON object describing one request; two identical requests are indistinguishable
The chatId→tenant/API-key mappingRepositoryIt mediates persistent storage and retrieval, and its implementation may be the JSON/YAML configuration file or the optional SQLite database — a swap the rest of the system should not notice
Parsing, routing, replyingServicesStateless capabilities that wire the other objects together for one use case each

The repository row is the interesting one, because the brief itself offers two storage options — a configuration file, or an optional SQLite database. Chapter 4 has a name for an interface designed so that both fit behind it without leaking: an anti-corruption layer, and the third DDD exercise (the CSV one) is exactly the drill for it, down to the test that must pass unchanged against two implementations.

The meta-language is a DSL

Component 2 asks for an intermediate language for commands plus a parser producing standardised JSON objects. In the vocabulary of Chapter 5 this is an external DSL: a custom concrete syntax with a custom parser, whose output is a model instance. The design questions the chapter supplies transfer directly:

How the project would meet the exam requirements

Exam requirement (Ch. 1)What the brief already providesWhat the team must add
Domain-driven designA domain description in the client's own words, and a vocabularyGlossary, context map, model named after the language, building-block choices
Clear process and DevOps practicesDocumented source and a technical README among the deliverablesA visible workflow in the repository
Full-scale automation, including CI/CDThe whole pipeline: this is not requested by the brief and is required by the exam
Deploy automation via containerizationA startup script or Dockerfile is an expected deliverableWiring the image into the pipeline
Two or more target platformsThe brief suggests either a Python stack or a Node.js stackNote the criterion: CPython and NodeJS are different runtimes, so a system whose components genuinely run on both satisfies the rule — choosing one stack for everything does not
For the exam

A brief like this one is the natural object of the oral discussion, because every course topic has a foothold in it. Be ready to say: which words you put in the glossary and why; which concepts became entities rather than value objects; where you drew the context boundary between the channel side and the backend side; why the command language is a DSL and where its validation rules live; how the chatId-to-tenant mapping is stored and how you kept that decision swappable; and how the four security constraints are enforced by construction rather than by discipline.

Check your understanding

State the four things the ChatFlow middleware must do.

Receive messages from messaging systems (WhatsApp and Telegram); translate the messages into an intermediate language of meta-commands; forward the requests to a multi-tenant REST management backend through secure APIs; and return the response to the original sender, on the correct channel.

Name the five required components.

(1) Handling input from the messaging channels (Telegram Bot API, WhatsApp Cloud API or a simulation); (2) definition and parsing of the meta-language, with a parser producing standardised JSON objects; (3) routing the requests to the management backends, with one API key per tenant and an Authorization header; (4) returning the response, formatted, on the originating channel; (5) configuration and security, with the chatId-to-tenant/API-key mapping, safe logging and rate limiting.

What are the four security constraints, and which is the most consequential?

No sensitive data stored in clear; a dedicated API key per tenant; the middleware does not handle end-user authentication; logging only for debug, with obfuscated data. The third is the most consequential: it removes user credentials from the system entirely, and in exchange makes the chatId mapping the sole determinant of whose data is being accessed.

Why is the chatId-to-tenant mapping the heart of the system?

Because chatId is a channel-side identifier that means nothing to the backend, while tenant is a backend-side identity with an API key attached. The mapping is the point of contact between two contexts, and it is what decides on whose behalf every request is made — especially given that the middleware performs no end-user authentication of its own.

Walk through the complete cycle of the example.

The user sends /riepilogo 2025 on Telegram; BridgeBot receives the message with chatId: 654321; it maps that id to the tenant ComuneXYZ; it retrieves the API key and sends the REST request; it receives { 'ore_totali': 124, 'assente': false }; and it replies to the user with the sentence "Nel 2025 hai totalizzato 124 ore. Nessuna assenza registrata."

Why is "formatting the response" listed as a component rather than left implicit?

Because the deliverable to the user is prose, not JSON. The backend answers with a data structure; the user receives a sentence. That transformation is a named responsibility of the middleware, and it is also where the optional extension "replies in multiple languages" would attach.

What are the expected deliverables?

Documented source code; a configuration file in JSON or YAML; a startup script or Dockerfile; a technical README; and, optionally, a web UI for configuration.

In the vocabulary of Chapter 5, what kind of language is the meta-language, and what follows from that?

An external DSL: a fully custom syntax, hence requiring a custom parser, producing a model (the standardised JSON objects). It follows that its constructs should be few and static, that it need not be Turing-complete, and that parsing and validation are different jobs — a syntactically valid command may still be meaningless, and rejecting it is the validator's task.

Which storage options does the brief leave open, and how should the design react?

The mapping and the logs may live in the JSON/YAML configuration file that is an expected deliverable, or in an optional SQLite database. Since the brief itself offers two, the design should hide the choice behind an interface — a repository acting as an anti-corruption layer — so that switching does not perturb the rest of the model.

Does building ChatFlow automatically satisfy the exam requirements?

No. It supplies a real domain to model, and a Dockerfile is already among its deliverables, but the brief says nothing about continuous integration and delivery or about a visible development process — those must be added. On platforms, note that the brief suggests either a Python stack or a Node.js stack: since CPython and NodeJS are different runtimes, genuinely targeting both satisfies the two-platform rule, while picking one stack for everything does not.

Why does the brief mention modularity, and how does it connect to Chapter 4?

It lists modularity, to divide the work among students among the implementation suggestions. That makes the decomposition an organizational matter as much as a technical one — exactly the third perspective from which Chapter 4 says a context boundary is real, and the reason for the best practice one responsible person or team for each context.

Which optional extensions are listed, and which one changes the input pipeline most?

A web UI for tenant management; support for voice messages with transcription; Telegram interactive buttons; and replies in multiple languages. Voice-to-transcription changes the input pipeline most: it inserts a new, fallible stage before parsing, and produces text that no longer necessarily conforms to the command syntax.