Last active
August 30, 2020 17:20
-
-
Save webdevilopers/d1d4ad278f01a17d47fda573260de3a5 to your computer and use it in GitHub Desktop.
Merge event-sourced aggregate roots (A+ES) by passing read model / decision model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
final class EmploymentContract extends AggregateRoot | |
{ | |
/** | |
* @param Details $contractToMerge | |
* This contract is always the initial contract and the oldest one. | |
* Since the contract to merge is always newer it always overwrites the current state. | |
*/ | |
public function mergeWith(Details $contractToMerge): void | |
{ | |
$mergeWithContractId = EmploymentContractId::fromString($contractToMerge->contractId()); | |
if ($this->isMergedWith($mergeWithContractId)) { | |
throw new AlreadyMergedWithEmploymentContractException(); | |
} | |
// Always use the latest employment end date. | |
$newEmploymentPeriod = EmploymentPeriodMerger::merge([ | |
$this->employmentPeriod, | |
new EmploymentPeriod($contractToMerge->startDate(), $contractToMerge->endDate()) | |
]); | |
$newProbationaryPeriod = null; | |
if (null !== $contractToMerge->probationEndDate()) { | |
// Since we always use the latest employment end date any probation end date will fit. | |
$newProbationaryPeriod = ProbationaryPeriod::withEmploymentPeriod( | |
$newEmploymentPeriod, $contractToMerge->probationEndDate() | |
); | |
} | |
$this->recordThat(MergedWithEmploymentContract::with( | |
$this->id, | |
$mergeWithContractId, | |
ContractType::fromString($contractToMerge->contractType()), | |
$newEmploymentPeriod, | |
$newProbationaryPeriod, | |
WorkerCategory::fromString($contractToMerge->workerCategory()), | |
WageType::fromString($contractToMerge->wageType()), | |
$contractToMerge->workingTimeAccount(), | |
WorkweekDays::fromArray($contractToMerge->workweekDays()), | |
WeeklyWorkingHours::fromFloat($contractToMerge->weeklyWorkingHours()), | |
HolidayEntitlement::fromInteger($contractToMerge->holidayEntitlement()), | |
AdditionalLeave::fromInteger($contractToMerge->additionalLeave()), | |
JobFunctionId::fromInteger($contractToMerge->jobFunctionId()), | |
$contractToMerge->jobFunctionName(), | |
EmployerId::fromString($contractToMerge->employerId()), | |
$contractToMerge->employerName(), | |
WorkplaceId::fromString($contractToMerge->workplaceId()), | |
$contractToMerge->workplaceName(), | |
new DateTimeImmutable() | |
)); | |
} | |
private function isMergedWith(EmploymentContractId $contractId): bool | |
{ | |
foreach ($this->mergedWithContractIds as $mergedWithContractId) | |
{ | |
if ($mergedWithContractId->sameValueAs($contractId)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
final class EmploymentContractMergeService | |
{ | |
/** @var DetailsRepository */ | |
private $detailsRepository; | |
/** @var EmploymentContractRepository */ | |
private $contractRepository; | |
public function __construct( | |
DetailsRepository $detailsRepository, | |
EmploymentContractRepository $contractRepository | |
) | |
{ | |
$this->detailsRepository = $detailsRepository; | |
$this->contractRepository = $contractRepository; | |
} | |
/** | |
* @param PersonId $personId | |
* @param EmploymentContractId[] $mergeWithContractIds | |
*/ | |
public function with(PersonId $personId, array $mergeWithContractIds): void | |
{ | |
$mergeWithContracts = []; | |
foreach ($mergeWithContractIds as $mergeWithContractId) { | |
$mergeWithContractDetails = $this->detailsRepository->ofId($mergeWithContractId->toString()); | |
$mergeWithContracts[] = $mergeWithContractDetails; | |
} | |
// Ensure chronological order of start of employment in order to detect the initial contract. | |
uasort($mergeWithContracts, function(Details $a, Details $b) { | |
return $a->startDate() <=> $b->startDate(); | |
}); | |
// The initial contract will be used for merging. | |
$initialContractId = $mergeWithContracts[0]->contractId(); | |
$initialContract = $this->contractRepository->get(EmploymentContractId::fromString($initialContractId)); | |
unset($mergeWithContracts[0]); | |
// Start the actual merging. | |
foreach ($mergeWithContracts as $mergeWithContract) { | |
// Pass read (decision) model to aggregate root which holds the actual merging logic / decisions. | |
$initialContract->mergeWith($mergeWithContract); | |
} | |
$this->contractRepository->save($initialContract); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Came from: