Birthday Greetings Kata
The Birthday Greetings Kata by Matteo Vaccari is a good way to think about decoupled design and the “Hexagonal Architecture”.
Problem
I’ll quote directly from the original blogpost on the subject.
Problem: write a program that
- Loads a set of employee records from a flat file
- Sends a greetings email to all employees whose birthday is today
The flat file is a sequence of records, separated by newlines; this are the first few lines:
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
The greetings email contains the following text:
Subject: Happy birthday! Happy birthday, dear John!
with the first name of the employee substituted for “John”.
The program should be invoked by a main program like this one:
public static void main(String[] args) { ... BirthdayService birthdayService = new BirthdayService( employeeRepository, emailService); birthdayService.sendGreetings(today()); }
Note that the collaborators of the birthdayService objects are injected in it. Ideally domain code should never use the new operator. The new operator is called from outside the domain code, to set up an aggregate of objects that collaborate together.
Breaking the problem down.
Implementing the solution involves breaking the problem into various pieces that can be implemented, and then using those pieces to solve the problem.
The definition of the problem breaks down into two different components:
- Load a set of employee records from a flat file
and
- Send a greetings email to all employees whose birthday is today
Employee model
Based on the sample data provided, the Employee
model is simple.
data class Employee(val lastName: String, val firstName: String, val dateOfBirth: LocalDate, val email: String)
These fields are taken directly from the sample CSV file.
Testing the employee birthday
Employees are simple in this problem, we really only need to know if any given date is “their birthday”, and tests for this and the implementation are fairly trivial.
internal class EmployeeTest {
val employee = Employee("test", "test", LocalDate.of(1990, 2, 5), "test@example.com")
@Test
fun isBirthdayWithIncorrectDate() {
assertFalse(employee.isBirthday(LocalDate.of(2019, 5, 9)))
}
@Test
fun isBirthdayWithCorrectDate() {
assertTrue(employee.isBirthday(LocalDate.of(2019, 2, 5)))
}
}
The simple data class grows a single function…
data class Employee(val lastName: String, val firstName: String, val dateOfBirth: LocalDate, val email: String) {
fun isBirthday(date: LocalDate): Boolean {
return Pair(date.dayOfMonth, date.monthValue) == Pair(dateOfBirth.dayOfMonth, dateOfBirth.monthValue)
}
}
The only interesting thing here is that it instiantiates two “tuples” (Pair
) objects for comparisons.
Employee repository
Reading the specification, it’s clear that there’s a need for a way to get the list of employees, and the sample code makes reference to an employeeReference
dependency.
In the absence of a better name, I’ll go with an EmployeeRepository
interface.
interface EmployeeRepository {
fun getAll(): Sequence<Employee>
}
I’ve opted to use a Sequence
for the return type, read more about Kotlin’s sequences here
for why, but the upshot is that they’re lazier than an Iterator
, for this simple case it probably won’t make much of a performance difference, but Sequence
seems more scalable.
import org.junit.Assert.assertEquals
import org.junit.Test
internal class FileEmployeeRepositoryTest {
@Test
fun `reading from a data file`() {
val filename = fixturePath("fixtures/employee_data.txt")
val repository = FileEmployeeRepository(filename)
val employees = repository.getAll()
assertEquals(2, employees.count())
}
@Test
fun `parsing an Employee record from a data file`() {
val filename = fixturePath("fixtures/employee_data.txt")
val repository = FileEmployeeRepository(filename)
val employees = repository.getAll()
val employee = Employee(
"Doe",
"John",
LocalDate.of(1982, 10, 8),
"john.doe@foobar.com"
)
assertEquals(employee, employees.first())
}
private fun fixturePath(filename: String) =
javaClass.classLoader.getResource(filename).path
}
The basic implementation looks like this…
class FileEmployeeRepository(val filename: String) : EmployeeRepository {
private val formatter = DateTimeFormatter.ofPattern("y/M/d")
override fun getAll(): Sequence<Employee> {
return File(filename)
.bufferedReader()
.lineSequence()
.drop(1)
.map(this::parseEmployee)
}
private fun parseEmployee(line: String) : Employee {
val (lastName, firstName, date, email) = line.split(",").map(String::trim)
return Employee(lastName, firstName, LocalDate.parse(date, formatter), email)
}
}
Looking first at getAll()
, this is a functional implementation of the parsing.
- Open the file and create a “buffered reader”
- Create a
lineSequence
from thebufferedReader
- this means that the parsing will be lazy. drop
the first line (this is the header row in the file)- apply (
map
)this::parseEmployee
to each line and return the result.
And parseEmployee
isn’t particularly complex.
val (lastName, firstName, date, email) = line.split(",").map(String::trim)
This splits the line on commas, and trims any whitespace from each of the items.
The other part, is using Kotlin’s destructuring to extract the elements.
The second line just instantiates an Employee with the various fields extracted.
So far, I’ve parsed a CSV file and instantiated Employee data records for each entry, in the next part, I’ll look at the email sending part of the problem.