I'm currently developing an application in ASP.NET Core 2.2 with Domain Driven Design. What you see in here is a Unit Creation page.
A Unit is composed of competences. A unit must have at least one competence and its name must not be in blank.
A Competence must have a descriptor, at least one indicator, and three evidence (one of the types shown above).
Once inserted into the DB, they're read by another Bounded Context which is called evaluations.
Competences, Indicators, and Evidence need to have Ids (They're going to become Entities) since they will be need to be fetched individually and referenced for a further evaluation operation.
Competences, Indicators, and evidences are all used separately in another bounded context called "Evaluation".
I'm looking for some feedback on how I structured these aggregates (They are simple), and if the Evidence, Indicators, and Competences are OK to be entities instead of value objects, considering that for "Unit Bounded Context" (The one shown above) doesn't necessarily view them as entities (It operates them all as part of the unit). But then, the "Evaluation Bounded Context" does see them as Entities.
I'm modeling them like entities so I can insert in the data store the right Ids and references immediately (Even though they are separate Bounded Context they all share the same data store).
Here are the aggregates so far (I haven't finished adding the domain logic)
public class Unit
{
public UnitId Id { get; private set; }
public string Name { get; private set; }
public List<Competence> Competences { get; private set; }
public Unit(UnitId Id, string Name, List<Competence> Competences)
{
Validate.AssertArgumentNotEmpty(Name, "Name cannot be blank");
Validate.AssertArgumentNotNull(Competences, "Competences can't be null");
Validate.AssertArgumentEnumarationNotEmpty(Competences, "There must be at least one Competence");
Name = Name;
Competences = Competences;
}
}
public class Competence
{
public CompetenceId Id { get; set; }
public string Descriptor { get; set; }
public string Name { get; set; }
public List<Indicator> Indicatores { get; set; }
public List<Evidence> Evidences { get; set; }
/// <summary>
/// When the Competence is created, a new "CompetenceCreatedEvent" shall be dispatched that needs
/// to be read by the message broker and get it to the other bounded context so it can be read.
/// </summary>
/// <param name="id"></param>
/// <param name="Name"></param>
/// <param name="descriptor"></param>
public Competence(CompetenceId id, string Name, string descriptor, List<Indicator> Indicatores, List<Evidence> Evidences)
{
Validate.AssertArgumentNotEmpty(Name, "Name can't be blank");
Validate.AssertArgumentNotEmpty(descriptor, "Descriptor can't be empty or null");
Validate.AssertArgumentNotNull(Indicatores, "Indicatores can't be empty");
Validate.AssertArgumentNotNull(Evidences, "Evidences can't be empty");
Validate.AssertArgumentEnumarationNotEmpty(Indicatores, "Indicatores can't be empty");
Validate.AssertArgumentEnumarationNotEmpty(Evidences, "Evidences can't be empty");
Indicatores = Indicatores;
Evidences = Evidences;
CheckThatEachEvidenceTypeIsPresent();
}
private void CheckThatEachEvidenceTypeIsPresent()
{
var allEvidencesArePresent =
Evidences.Any(x => x.TipoEvidence == TipoEvidence.Conocimiento) &&
Evidences.Any(x => x.TipoEvidence == TipoEvidence.Desmepeno) &&
Evidences.Any(x => x.TipoEvidence == TipoEvidence.Producto);
if (!allEvidencesArePresent)
{
throw new NotAllEvidencesArePresent();
}
}
}
public class Evidence
{
public string Id { get; set; }
public string IstrumentId { get; set; }
public string Description { get; set; }
public TypeEvidence TypeEvidence { get; set; }
public Evidence(string id, string instrumentId, string Description, TypeEvidence typeEvidence)
{
Validate.AssertArgumentNotEmpty(Description, "Description can't be empty");
Validate.AssertArgumentNotEmpty(instrumentId, "instrumentId can't be empty");
Id = id ?? Guid.NewGuid().ToString();
InstrumentId = instrumentId;
Description = Description;
TypeEvidence = TypeEvidence;
}
}
/**
* In a true DDD scenario, in this bounded context,
* Indicator would be a Value Object. A domain event would be published
* once the Indicator has been created that would automatically
* create an entity in another bounded context. "It would duplicate the data".
* **/
public class Indicator : IAggregate
{
public Guid Key { get; private set; }
public string Description { get; set; }
public Indicator(string Description)
{
Validate.AssertArgumentNotEmpty(Description, "Description can't be empty");
}
}
I need some helps verifying the aggregates' structure. In addition, I have some concerns:
Rephrasing what I said above: A unit is the aggregate root that's going to be saved (They work together in a transaction). That's why it has nested all those other aggregates. Right now, they are modeled as Entities, but I also think that in this bounded context they could potentially be modeled as Value Objects instead. Since the "Evaluation Bounded Context" is the one that needs to have them referenced. Therefore, the approach would be that once the Unit is created or modified, an Event is fired that communicates with the other bounded context, and the other bounded context is the one who performs the... Would this be the right approach? Or could I model it as an Entity from the Unit bounded context? (I understand that's a domain question, I just don't want to be adding additional layers of complexity for no special reason)
From this question that I asked, is it OK for me to pass the Event Bus via a dependency in the needed aggregates?