On 15 Aug 2021, at 10:47, Alexey Raga <alexe...@gmail.com> wrote:
Hi All!
--
You received this message because you are subscribed to the Google Groups "DDD/CQRS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/7e897065-55ee-42a7-9cc3-9b35ad7eb9b1n%40googlegroups.com.
Yep, I would find a way to disregard them if they were submitted, and then I’d check how many Performances had been submitted for a given Task in the user interface and hide the controls to add a new Performance. That leaves a very small chance that too many Performances would be submitted and the user will know that they shouldn’t submit more than X performances.
For the evaluation, there’s a few ways to do it. What triggers a Task’s Performances to be evaluated? Does a human press a button, is there a timeout, after the 3rd submission something else entirely?
Harrison
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/c9d415e5-580b-4f67-81e0-bf9c716376b2n%40googlegroups.com.
> Do you mean something like an Application Service consulting a projection? Don't even encode this rule as a part of any aggregate?
Because of data inconsistency, you cannot use a projection or read model.
What is done in these cases is to create a support table. That would contain in your case, for example, table attempts (userid, number of attempts) every time you try, you must read this table to see how many attempts you have. Then you must update this table with the data. When you reach the limit of attempts, then what you do is to return the corresponding error because you reached the number of attempts.
Where does this logic go?
In my case I have done it in the command handler, but you can also do it in an application service.
Greetings.
--
You received this message because you are subscribed to a topic in the Google Groups "DDD/CQRS" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/dddcqrs/3YkbuiN-OGg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to dddcqrs+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/83b32ed1-edf9-449f-9228-31170faeec0en%40googlegroups.com.
Yes, doing something like this does feel somehow ‘wrong’ but when you absolutely have to do set validation having a separate table that you look at is a sensible pragmatic approach, otherwise the only option you’re left with is to put everything into a single massive aggregate which is obviously wrong.
When I do have to do this I do prefer to put it in the domain model rather than in the command handler. I’d use double dispatch something like the pseudo code shown below.
However, as I say you can often find that you don’t actually need to do cross-aggregate validation. There is already an interesting discussion on that topic here which you should read: https://groups.google.com/g/dddcqrs/c/aUltOB2a-3Y/m/0p0PQVNFONQJ (including comments from Greg Young who invented event sourcing)
I’d also recommend you watch this presentation which covers related topics: https://verraes.net/2013/06/unbreakable-domain-models/
Note that even that code doesn’t guarantee that performances cannot exceed the limit because of concurrency. Handling strict concurrency is a pain because you end up having to do locking or something, hence why I tend to avoid attempting to actually do set validation in DDD and rather do looser validation further up the stack (e.g., in the UI or HTTP controller) and then add code to handle the <1% of times one does slip through. In your case, that might mean having some code which listens for a performance that sneaks through your validation and revokes the grade applied to a Task if it was given by an invalid Performance submission.
Doing this does leak some business knowledge out of your domain so you might want to look at the Domain Query pattern or Specification pattern.
namespace MyApp\Domain;
class Task {
const PERFORMANCE_LIMIT = 3;
// Using the Task aggregate as a factory for Performances
// to give an expressive domain model
public function submitPerformance(
PerformanceContent $performanceContent,
PerformanceRepository $performances
) : Performance
{
if ($performances->countForTask($this) > self::PERFORMANCE_LIMIT) {
throw CannotSubmitMorePerformancesForTask($this, $performanceContent);
}
return new Performance($this->id(), $performanceContent);
}
}
interface PerformanceRepository {
public function countForTask(Task $task) : int
}
// .............
namespace MyApp\Infrastructure;
class SqlPerformanceRepository implements PerformanceRepository {
public function countForTask(Task $task) : int
{
return DB::table('performances')
->where('task', '=', $task->id()->toString())
->count();
}
}
Harrison
To view this discussion on the web visit https://groups.google.com/d/msgid/dddcqrs/b0f93d5e-94d3-48f7-a105-dfc6c60f4a1an%40googlegroups.com.