rgoussu@goussu: ~/library/applicative-architecture/exercises
~/library/applicative-architecture/exercises cat birthday-greetings-hexagonal-subject.md

Birthday Greetings — hexagonal extraction — subject

# The self-contained Birthday Greetings work statement — the greeting behavior, the flat starting design, the extraction assignment, and the checks that prove the boundary holds.

Subjectsaved 2026-08-08source #exercise#hexagonal-architecture#ports-and-adapters#tdd#design#subject

Brief

You maintain a tiny office utility that runs every morning: it reads the employee list from a flat file and emails a birthday greeting to everyone whose birthday is today. It works, everyone likes it — and it is one class that does everything: file parsing, date logic, greeting text, SMTP. Nobody can test it without a mail server, and the office is about to move the employee list into a database. Your assignment is to refactor it into a hexagon — pure domain core behind ports, technology in adapters — and then prove the boundary by swapping the technology without touching the core.

Instructions

The behavior (unchanged throughout)

  • Employees live in employees.csv, one per line, header included: last_name, first_name, date_of_birth, email, dates as YYYY/MM/DD.
  • On each run, find every employee whose birthday — month and day of date_of_birth — matches the run date.
  • Send each of them an email: subject Happy birthday!, body Happy birthday, dear <first_name>!.
  • Employees with no birthday today get nothing; an empty match sends nothing.

The starting design (build it or take the kata's)

One class, no seams: it opens the file, parses lines, compares dates, builds the message, and talks to the mail library directly. This flat version is milestone 1 — ugly on purpose.

The extraction assignment

  1. Build (or adopt) the naive flat version above.
  2. Pin the behavior with one end-to-end acceptance test at the system edges: a fixture CSV in, a fake SMTP server or captured output out.
  3. Untangle the pure logic — "who has a birthday on date X" and the greeting content — from all I/O. No interfaces yet.
  4. Extract two driven ports owned by the core: EmployeeRepository (provides employees) and GreetingSender (delivers greetings). The CSV reader and the mail sender become adapters implementing them.
  5. Unit-test the core against in-memory fakes; write one contract test per port that both the real adapter and the fake must pass.
  6. Add a second adapter pair — an SQLite EmployeeRepository and a console or Slack GreetingSender — selected purely by wiring at startup.

Examples

employees.csv:

last_name, first_name, date_of_birth, email
Doe, John, 1982/10/08, john.doe@foobar.com
Ann, Mary, 1975/09/11, mary.ann@foobar.com

A run dated October 8 sends exactly one email:

To:      john.doe@foobar.com
Subject: Happy birthday!

Happy birthday, dear John!

A run dated October 9 sends nothing.

Constraints

  • Behavior stays identical through every step — the acceptance test from step 2 must pass unchanged from milestone 2 onward.
  • After step 4, the core imports nothing from infrastructure: no file, SQL, or mail types cross the ports; the interfaces speak domain language (employees, greetings).
  • The final swap (step 6) may only touch adapter code and wiring — a single changed line in the core means the boundary is a fiction.
  • Hand-roll the fakes; no mocking framework needed at this size.

Acceptance

Mapped to the exercise's milestones:

  1. The flat version reproduces the example above end to end.
  2. The acceptance test runs the whole program against a fixture CSV and asserts on the captured outgoing messages — and fails if the greeting text or recipients change.
  3. The birthday rule and greeting content are callable with plain values — no file or mail object in sight.
  4. EmployeeRepository and GreetingSender are defined in the core's package/module; dependency arrows all point inward.
  5. Core unit tests run without touching disk or network; each port has a contract test green against both its real adapter and its fake.
  6. Switching CSV→SQLite and SMTP→console is a wiring-only change with zero core diffs, and the acceptance test still passes.

Related