This is an overly simplistic example of a domain model for a time keeping domain. I only started on it today as a way to practice the techniques I'm learning while reading Implementing Domain-Driven-Design by Vaughn Vernon.

I am using the Axon framework to model my Aggregate (posted below).

One big question I had in my Design was the TimeCardEntry objects. Is it appropriate for me to keep a running list of Entries? Should they contain an ID at all or even be an AggregateMember?

I am still working through the details, but I imagine eventually I will need to add a query layer that will allow viewing a timecard's data.

I will also likely eventually need an ability to udpate existing time card entries via an UpdateTimeCardEntry command.

public class TimeCard {

  private String employeeName;

  @AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)
  private List<TimeCardEntry> timeCardEntries = new ArrayList<>();

  public TimeCard() {
    //Empty Constructor for Axon framework

  public TimeCard(ClockInCommand cmd) {
    apply(new ClockInEvent(cmd.getEmployeeName(),

  public void handle(ClockOutCommand cmd) {
            entry -> apply(
                new ClockOutEvent(cmd.getEmployeeName(),
            () -> log.error("Employee has not clocked in or is already clocked out"));

  public void on(ClockInEvent event) {
    this.employeeName = event.getEmployeeName();
    timeCardEntries.add(new TimeCardEntry(event.getTimeCardEntryId(), event.getTime()));

  private Optional<TimeCardEntry> getEntryIfOpen(String timeCardEntryId) {
    return timeCardEntries.stream()
        .filter(entry -> entry.getTimeCardEntryId().equals(timeCardEntryId))

  public class TimeCardEntry {

    private final String timeCardEntryId;
    private final Instant clockInTime;
    private Instant clockOutTime;

    public void on(ClockOutEvent event) {
      this.clockOutTime = event.getTime();

    private boolean isClockedIn() {
      return clockOutTime == null;

I have the following tests -

class TimeCardTest {

  AggregateTestFixture<TimeCard> testFixture;

  void setUp() {
    testFixture = new AggregateTestFixture<>(TimeCard.class);

  @MethodSource(value = "randomEmployeeName")
  void testClockInCommand(String employeeName) {
    String randomUUID = UUID.randomUUID().toString();
        .when(new ClockInCommand(employeeName, randomUUID))
        .expectEvents(new ClockInEvent(employeeName, testFixture.currentTime(), randomUUID));

  @MethodSource(value = "randomEmployeeName")
  void testClockOutCommand(String employeeName) {
    String randomUUID = UUID.randomUUID().toString();
        .givenCommands(new ClockInCommand(employeeName, randomUUID))
        .when(new ClockOutCommand(employeeName, randomUUID))
        .expectEvents(new ClockOutEvent(employeeName, testFixture.currentTime(), randomUUID));

  private static Stream<String> randomEmployeeName() {
    return Stream.generate(RandomString::make).limit(10);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.