Recommended resources
The freely available resources this lesson will direct you to.
Hello! Last lesson defined what the note-review system must do: ingest notes without duplicates, assemble a stable daily queue, accept recall feedback once per submission, and avoid duplicate reminder attempts. Now we will decide which parts of the system are responsible for those behaviors.
This lesson uses a component diagram to make those boundaries visible. Plan on about 40 minutes: roughly four minutes of video, five minutes of reading, and the rest studying the proposed diagram and checking it against the behavioral contract. The diagram is a design proposal for a local implementation, not a claim that every deployment choice is settled.
Choose the right level of detail
A diagram of only “FastAPI, PostgreSQL, GitHub, and Telegram” would identify technologies but leave the hardest question unanswered: where do ingestion rules, scheduling decisions, and reminder safeguards live? At the other extreme, drawing every Python class would obscure the architecture.
The C4 model offers a useful middle level. A container is a running application or data store; a component is a group of related functionality behind an interface inside a container. A component does not have to be separately deployed.
Visualising software architecture with the C4 model - Simon Brown, Agile on the Beach 2019
Watch “Visualising software architecture with the C4 model,” presented by Simon Brown on Agile on the Beach, to see how a component view fits among broader architecture views.
Start with the levels, where the speaker introduces context, containers, components, and code as different degrees of detail. Then watch the component view: notice that the diagram opens up one API application while keeping its database and other systems outside that application’s boundary.
The following C4 example is worth inspecting for its boundaries, not its banking-specific design. The backend contains API and security components, while its data stores and external services remain distinct.

Read the C4 model’s “Component” page to distinguish an architectural component from a deployable application or an individual class.
Under “Component,” read the opening explanation, ending where it says components inside a container execute in the same process space. Focus on the difference between a responsibility grouped behind an interface and a separate running process.
For our design, the FastAPI backend is one container. Its note reader, scheduling policy, and Telegram adapter are components within it. PostgreSQL is another container: it is a separate data store, not a Python component inside FastAPI.
Set the system boundary before drawing boxes
The source of notes is a locally synchronized Markdown repository. Editing, synchronization, ingestion, and review are related activities, but they do not all belong to the same process.
Here is the proposed local arrangement:
- Obsidian edits Markdown files; GitHub hosts the remote Git repository.
- A Git sync process updates a local checkout. It does not write review records to PostgreSQL.
- The FastAPI backend reads that checkout and owns review behavior.
- PostgreSQL persists ingested notes, review history, scheduling state, reminder-attempt records, and, when generated, embedding vectors.
- The Telegram Bot API is outside the local system. A backend adapter communicates with it.
For this diagram, the embedding service means Python functionality running in the backend process with a locally available model. Using a hosted embedding provider instead would change a deployment boundary and add an external relationship. Neither option changes who decides whether a note needs a new embedding: that decision belongs to backend application logic.
The arrows also need a convention. Here, an arrow points from the caller or updater toward the thing it depends on; it does not depict every request and response or prescribe the order of an entire workflow.
Read the C4 model’s “Notation” guidance for the information a diagram needs in order to stand on its own.
In “Diagrams,” read title and key. In “Elements,” read element descriptions. Finally, under “Relationships,” read arrow labels and protocol labels. Notice how much meaning the words carry without relying on color.
Component diagram for the local review backend
This diagram deliberately opens up one container—the review backend. It shows the neighboring containers and external systems only where they explain an important boundary.
Diagram key: Bracketed text states each element’s C4 type; text after it gives its technology and principal responsibility. The outer boundary is the local note-review system. Boxes inside the Review backend boundary are components in one proposed backend process. A directed line identifies a dependency or update, and its label names the interaction. For example, the database line describes a cross-process connection; the line to the scheduling policy describes an in-process call.
This is a static responsibility map, not a timeline. The two relationships involving the Telegram adapter represent different reasons to involve the same component: it supplies received callback ratings to review services and delivers messages requested by those services. For the local arrangement shown, it polls Telegram for updates; a webhook-based deployment would require a different entry-point relationship.
Test the ownership against the four workflows
Note ingestion. Obsidian and Git synchronization are responsible for getting Markdown into the local checkout. They do not decide whether two files with the same note_id constitute a conflict. The backend’s note reader parses and validates source metadata; review services decide what to ingest or reject; the repository persists the result. If a sync fails, the backend must not interpret that as an intentionally empty vault. Keeping sync separate from ingestion makes that distinction possible.
Daily queue. The HTTP API accepts a request and returns a response. Review services apply the agreed queue contract—pending items due before the end of the configured day, including overdue items—and ask the repository for the relevant persisted data. PostgreSQL performs the data query; it does not define the product meaning of “today” on its own. Under last lesson’s working decision, the review timezone is UTC and ties are resolved by note_id. The detailed timezone-aware query comes later.
Recall feedback. An HTTP request or Telegram callback reaches review services. The services check that the rating is supported and that the submission key represents an eligible new submission or a retry. They consult the scheduling policy for the next state, then use the repository to record the accepted review and state change. The scheduling policy computes a result; it neither opens a database connection nor sends a Telegram message. This separation will let you test scheduling rules without running FastAPI or PostgreSQL.
Reminder. The reminder runner starts a configured daily window; it does not invent a separate definition of the due queue. Review services check that the queue is nonempty and coordinate a durable attempt claim through the repository before asking the Telegram adapter to send. For the previous lesson’s at-most-one-request proposal, a repeated run finds the existing claim and does not submit another request for that window. A crash or timeout can still leave delivery uncertain: this design does not promise that a message definitely arrived.
These ownership decisions expose a useful rule: an integration adapter knows how to communicate with another system; application logic knows why and when to do so. The Telegram adapter should not calculate intervals, and Git sync should not decide which notes are due.
What the diagram settles—and what it leaves open
The diagram is specific enough to guide implementation, but it avoids prematurely becoming a class diagram or database schema. It identifies one route into PostgreSQL: the backend repository. That makes it easier to locate persistence rules and transaction boundaries later. It also identifies a place for embeddings without implying that every ingestion needs to regenerate one; review services can request a vector when content has changed.
Some decisions still need their own design work. The diagram does not specify PostgreSQL tables, a scheduling formula, how a local model is packaged, or how a note removed from the vault should be treated. Those omissions are intentional. A component diagram should reveal ownership and dependencies, not silently settle every policy.
When reviewing a diagram like this, read its labels as sentences: “Review services request due state from the scheduling policy”; “The note reader reads Markdown from the synced vault.” If a line says only “uses,” or a box says only “manager,” the responsibility probably needs clarification. If a database, bot adapter, or HTTP route appears to make product-policy decisions, reconsider the boundary.
Conclusion
You now have a component-level design that separates source synchronization, backend workflow decisions, pure scheduling and embedding functionality, persistence, and Telegram communication. The important distinctions are that C4 components sit inside a container, external systems remain outside it, and every relationship should say what crosses the boundary.
Next, you will define the Python domain models for notes, review events, scheduling state, and reminder status. The boundaries established here will help keep those models independent of FastAPI and PostgreSQL.