pizzaminded
pizzaminded

Reputation: 31

DDD - how to deal with get-or-create logic in Application Layer?

I have an DailyReport Entity in my Domain Layer. There are some fields in this object:

Also, there are some methods in my Application Service:

The thing is that both of these methods require DailyReport to be created earlier or created during function execution. How to deal with this situation?

I have found 2 solutions:

1 Create new DailyReport object before method dispatching, and after that pass reportId to them:

//PHP, simplified
public function __invoke() {
    $taskData = getTaskData();

    /** @var $dailyReport DailyReport|null **/
    $dailyReport = $dailyReportRepository->getOneByDateAndUser('1234-12-12', $user);

    //there were no report created today, create new one
    if($dailyReport === null) {
        $dailyReport = new DailyReport('1234-12-12', $user);
        $dailyReportRepository->store($dailyReport);
    }

    $result = $dailyReportService->addTaskToDailyReport($taskData, $dailyReport->reportId);

    //[...]

}

This one requires to put a more business logic to my Controller which i want to avoid.


2: Verify in method that DailyReport exists, and create new one if needed:

//my controller method
public function __invoke() {
    $taskData = getTaskData();
    $result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);

    //[...]
}


//in my service:

public function addTaskToDailyReport($taskData, $date, $user) {

    //Ensure that daily report for given day and user exists:
    /** @var $dailyReport DailyReport|null **/
    $dailyReport = $dailyReportRepository->getOneByDateAndUser();

    //there were no report created today, create new one
    if($dailyReport === null) {
        $dailyReport = new DailyReport($date, $user);
        $dailyReportRepository->store($dailyReport);
    }

    //perform rest of domain logic here
}

This one reduces complexity of my UI layer and does not expose business logic above the Application Layer.


Maybe these example is more CRUD-ish than DDD, but i wanted to expose one of my use-case in simpler way.

Which solution should be used when in these case? Is there any better way to handle get-or-create logic in DDD?


EDIT 2020-03-05 16:21:

a 3 example, this is what i am talking about in my first comment to Savvas Answer:


//a method that listens to new requests 
public function onKernelRequest() {
    //assume that user is logged in

    $dailyReportService->ensureThereIsAUserReportForGivenDay(
        $userObject,
        $currentDateObject
    );
}


// in my dailyReportService:
public function ensureThereIsAUserReportForGivenDay($user, $date) {
    $report = getReportFromDB();

    if($report === null) {
        $report = createNewReport();
        storeNewReport();
    }


    return $report;
}



//in my controllers 
public function __invoke() {

    $taskData = getTaskData();

    //addTaskToDailyReport() only adds the data to summary, does not creates a new one
    $result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);

    //[...]
}

This will be executed only when user will log in for the first time/user were logged in yesterday but this is his first request during the new day. There will be less complexity in my business logic, i do not need to constantly checking in services/controllers if there is a report created because this has been executed previously in the day.

Upvotes: 2

Views: 463

Answers (1)

Savvas Kleanthous
Savvas Kleanthous

Reputation: 2745

I'm not sure if this is the answer you want to hear, but basically I think you're dealing with accidental complexity, and you're trying to solve the wrong problem.

Before continuing I'd strongly suggest you consider the following questions:

  1. What happens if someone submits the same report twice
  2. What happens if someone submits a report two different times, but in the second one, it's slightly different?
  3. What is the impact of actually storing the same report from the same person twice?

The answers to the above questions should guide your decision.

IMPORTANT: Also, please note that both of your methods above have a small window where two concurrent requests to store the rerport would succeed.

From personal experience I would suggest:

  1. If having duplicates isn't that big a problem (for example you may have a script that you run manually or automatically every so often that clears duplicates), then follow your option 1. It's not that bad, and for human scale errors should work OK.
  2. If duplicates are somewhat of a problem, have a process that runs asynchronously after reports are submited, and tries to find duplicates. Then deal with them according to how your domain experts want (for example maybe duplicates are deleted, if one is newer either the old is deleted or flagged for human decision)
  3. If this is part of an invariant-level constraint in the business (although I highly doubt it given that we're speaking about reports), and at no point in time should there ever be two reports, then there should be an aggregate in place to enforce this. Maybe this is UserMonthlyReport or whatever, and you can enforce this during runtime. Of course this is more complicated and potentially a lot more work, but if there is a business case for an invariant, then this is what you should do. (again, I doubt it's needed for reports, but I write it here in the care reports were used as an example, or for future readers).

Upvotes: 2

Related Questions