I am learning DDD and trying to model articles, its variants and parameters.
Article can be on it's own without variants
Variant must be child of an article
both article and variant can have some parameters (colors, brands, sizes...), physical quantities (width, length, some article-specific like inner length)
If you set some parameter on an article, it can be "synchronized" to it's children variants
you can override this in a variant by setting that parameter as "unlinked", then this variant would have different parameter value than article
some parameters can be set multiple times (color: red, blue), but some only once (brand)
those parameters are dynamically create, it's not a Color or Brand property but key-value selected from preconfigured values
I think my main aggregate roots will be Article and Variant.
My current code looks like this:
internal class Article : AggregateRoot<ArticleId>
{
private readonly ISet<VariantId> _variants = new HashSet<VariantId>();
private readonly ISet<AssignedParameter> _parameters = new HashSet<AssignedParameter>();
private readonly ISet<AssignedPhysicalQuantity> _physicalQuantities = new HashSet<AssignedPhysicalQuantity>();
public string Name { get; private set; }
public string Catalog { get; private set; }
public IReadOnlySet<VariantId> Variants => _variants.AsReadOnly();
public IReadOnlySet<AssignedParameter> Parameters => _parameters.AsReadOnly();
public IReadOnlySet<AssignedPhysicalQuantity> PhysicalQuantities => _physicalQuantities.AsReadOnly();
private Article(ArticleId id, string name, string catalog)
: base(id)
{
Name = name;
Catalog = catalog;
}
public static Article Register(ArticleId id, string name, string catalog)
{
var article = new Article(id, name, catalog);
article.AddEvent(new ArticleRegistered(article.Id, article.Name, article.Catalog));
return article;
}
public void AssignParameter(Parameter parameter, ParameterValue parameterValue, bool syncToVariants)
{
if (!parameter.CanBeAssignedMultipleTimes && _parameters.Any(p => p.ParameterId == parameter.Id))
{
throw new ParameterCanBeAssignedOnlyOnceException($"Parameter {parameter.Id} can by assigned only once.");
}
var assignedParameter = new AssignedParameter(parameter.Id, parameterValue.Id, syncToVariants);
if (!_parameters.Add(assignedParameter))
{
throw new ParameterIsAlreadyAssignedException($"Parameter {parameter.Id} with value {parameterValue.Id} is already assigned.");
}
AddEvent(new ArticleParameterAssigned(Id, assignedParameter.ParameterId, assignedParameter.ParameterValueId));
}
public void UnassignParameter(Parameter parameter, ParameterValue parameterValue)
{
var assignedParameter = _parameters.FirstOrDefault(p => p.ParameterId == parameter.Id && p.ParameterValueId == parameterValue.Id);
if (assignedParameter is null)
{
throw new ParameterIsNotAssignedException($"Parameter {parameter.Id} is not assigned.");
}
_parameters.Remove(assignedParameter);
AddEvent(new ArticleParameterUnassigned(Id, assignedParameter.ParameterId, assignedParameter.ParameterValueId));
}
// physical quantity assign / unassign are similar to parameters
}
internal class Variant : AggregateRoot<VariantId>
{
private readonly ISet<AssignedParameter> _parameters = new HashSet<AssignedParameter>();
private readonly ISet<AssignedPhysicalQuantity> _physicalQuantities = new HashSet<AssignedPhysicalQuantity>();
public string Name { get; private set; }
public string Catalog { get; private set; }
public EanCode Ean { get; private set; }
public decimal Weight { get; private set; }
public IReadOnlySet<AssignedParameter> Parameters => _parameters.AsReadOnly();
public IReadOnlySet<AssignedPhysicalQuantity> PhysicalQuantities => _physicalQuantities.AsReadOnly();
internal Variant(VariantId id, string name, string catalog, EanCode ean, decimal weight)
: base(id)
{
Name = name;
Catalog = catalog;
Ean = ean;
Weight = weight;
}
// parameter and physical quantity assignment methods
}
Parameters:
internal class Parameter : AggregateRoot<ParameterId>
{
private readonly ISet<ParameterValue> _values = new HashSet<ParameterValue>();
public string Code { get; private set; }
public string Name { get; private set; }
public bool CanBeAssignedMultipleTimes { get; private set; }
public IReadOnlySet<ParameterValue> Values => _values.AsReadOnly();
public Parameter(ParameterId id, string code, string name, bool canBeAssignedMultipleTimes)
: base(id)
{
Code = code;
Name = name;
CanBeAssignedMultipleTimes = canBeAssignedMultipleTimes;
}
}
internal class ParameterValue : Entity<ParameterValueId>
{
public string Code { get; private set; }
public string Name { get; private set; }
public Parameter Parameter { get; private init; } = null!;
public ParameterValue(ParameterValueId id, string code, string name)
: base(id)
{
Code = code;
Name = name;
}
}
Value objects:
// for Article, variant doesn't have SyncToVariants property and has some other
internal class AssignedParameter : ValueObject
{
public ParameterId ParameterId { get; private init; }
public ParameterValueId ParameterValueId { get; private init; }
public bool SyncToVariants { get; private init; }
public AssignedParameter(ParameterId parameterId, ParameterValueId parameterValueId, bool syncToVariants)
{
ParameterId = parameterId;
ParameterValueId = parameterValueId;
SyncToVariants = syncToVariants;
}
protected override IEnumerable<object> GetEqualityComponents()
{
yield return ParameterId;
yield return ParameterValueId;
}
}
internal class AssignedPhysicalQuantity : ValueObject { ... }
My questions:
What would be the best way to notify variants of the parameter change? I can think of two ways using events.
First would be using ArticleParameterChanged(ArticleId, parameter.Id, parameterValue.Id). I would handle this event and changed all variants at once in the handler - I don't think this is the way, but I wouldn't need to hold variants collection in article.
Second would be to loop through variant IDs and create ArticleVariantParameterChanged(ArticleId, VariantId, parameterId, parameterValueId) event. This seems more correct to me?
if (syncToVariants)
{
foreach (var variantId in _variants)
{
AddEvent(new ArticleVariantParameterChanged(Id, variantId, parameter.Id, parameterValue.Id);
}
}
How do I add new variant to article? The easiest way would be to create new variant and update the article in one transaction.
// Article method
public Variant RegisterVariant(VariantId variantId, ...)
{
var variant = new Variant(variantId, ...);
_variants.Add(variantId);
return variant;
}
// command handler? or domain service?
var article = await _articleRepo.GetAsync(articleId);
var variant = article.RegisterVariant(variantId, ...);
await _variantRepo.AddAsync(variant);
await _articleRepo.UpdateAsync(article);
Or using events?
// Article method
public Variant RegisterVariant(VariantId variantId, ...)
{
var variant = Variant.Register(variantId, this.Id, ...);
return variant;
}
// Variant static method
public Variant Register(VariantId variantId, ArticleId articleId, ...)
{
var variant = new Variant(variantId, articleId, ...);
variant.AddEvent(new VariantRegistered(variantId, articleId));
return variant;
}
// command handler
var variant = article.RegisterVariant(...);
await _variantRepo.AddAsync(variant);
// VariantRegisteredHandler
article.AddVariant(variantId);
However here it seems kind of confusing to me, article.RegisterVariant and article.AddVariant... Maybe it's just wrong naming?
Also here can occur condition race between adding new variant and assigning a new parameter, when someone adds new parameter before the VariantRegistered event was handled, so it wouldn't sync that parameter.
So I'm thinking, is it even good idea to store those shared parameters in each variant? Maybe it would be enough to just have variant specific parameters there and merge everything in the read model? However this would be harder to prevent duplications - if the article already has a parameter "color - red", assigning "color - red" to variant would need to check the article parameters too and there can be another race condition.
I read that entities without any domain business logic could be treated as CRUD, that means they wouldn't even inherit AggregateRoot and each of them would have own repository, right?
Let's say someone really wants to delete some parameter value, for example blue color. This wouldn't (hopefully) happen in my app, but I'm still curious how this would be handled. He confirms he really wants to delete it and I need to go through all articles and unassign it from them. How?
My idea would be either to have ParameterValueDeleted event and ParameterValueDeletedHandler would query for all articles and variants and unassign it one by one, this handler would take really long time to execute.
Or ParameterValueDeletedHandler would query for all IDs, create some event for them and that handler would unassign it later. However in the latter case I don't know how that event would be named to make sense. UnassignArticleParameter seems more like command than event and ArticleParameterUnassigned is something coming from article. Also I read that commands indicate something that can be rejected, so I would say command doesn't fit here.
Also I see a problem when someone deletes that parameter and someone else queries for an article which doesn't have it unassigned yet - database join would fail because it would join to non existent parameter (considering single database for read and write model).
If I wanted to have mandatory parameters, where would be the best place to validate that all of them are set? Move the article registration logic to ArticleFactory and check it there? And for variants maybe ArticleService or VariantFactory? This seems kinda inconsistent to me, but maybe it's right?
var article = await _articleRepo.GetAsync(articleId);
_articleService.RegisterVariant(article, /* variant creation data */);
_variantFactory.Register(article, /* variant creation data */);
I think this should be all, I hope I explained everything well.
I would appreciate any help with this!
I have a web api action method which takes below Model as parameter (Post).
public class RequestModel
{
public string PartType { get; set; }
public int Quantity { get; set; }
public decimal UnitCost{ get; set; }
public bool? Owner { get; set; }
public bool? DoSplit { get; set; }
}
The options Owner/Do Split will be choosen by the user on UI and its based on Part Type. Also based on the Owner flag there is some other business logic which needs to be executed in combination with the DoSplit and Quantity. Hence I have many permuations and combinations. Going bruteforce the logic would go this way:
int existingQty = GetInitialQuantity(model.SerialId); //returns esisting qty
if(existingQty < model.Quantity && model.Owner)
{
// logic here
}
else if (existingQty < model.Quantity && model.Owner == false)
{
}
else if (existingQty = model.Quantity) // no need to check for DoSplit
{
}
etc..... more if else in combincation with qty comaprison, Dosplit and owner flag checks with null checks.
based on the different property values in the model (in combination) I need to do different actions. How to avoid if else and use a proper design patterns of C# here.
Since the model is passed from javascript through a web api call to my action method how can I use OOPs here for the requestmodel and avoid branching in the controller method ?
I think one of the main reasons that you have so much if/else is that you do not have the business logic in the object itsel but try to use the business logic from outside. As I do not get what your business logic is, my implementation might not work on your case, but i want to show you how to get rid of the if else in a simple case. The main goal is to not use the properties but only use the functions and let the object handle its state on its own. (lookup Tell dont ask and State Pattern)
lets look at this class
public class User
{
string name { get; set; }
bool isDisabled { get; set; }
}
using it might be like this
if (!user.isDisabled)
{
user.name = nameFromApi
}
but in this case you have to repeat this on every corner where you want to use the User. So consider this
public interface IUser
{
string name { get; }
IUser updateName(string newName);
IUser disableUser();
}
public class DisabledUser : IUser
{
public DisabledUser(IUser activeUser)
{
this.name = activeUser.name;
}
public string name { get; }
public IUser updateName(string newName)
{
return this;
}
public IUser disableUser()
{
return new DisabledUser(this);
}
}
public class ActiveUser : IUser
{
public ActiveUser(IUser user)
{
this.name = user.name;
}
public string name { get; private set; }
public IUser updateName(string newName)
{
this.name = newName;
return this;
}
public IUser disableUser()
{
return new DisabledUser(this);
}
}
In this way the if is gone and you actually gained something else: You can easily extend the implementation with other states like banned and you do not have to change the old implementation. Yes, it is more code, but way easier to maintain.
In you case i think you should be able to get rid of all the if/elses regarding the boolean flags, if you initialize the objects correctly. This is a powerfull pattern and you do not need to cast anything to be able to use the user.
I do not know your usecase for the quantity stuff, so I can not tell you how this might get resolved, but there is certainly a way to do that.
Given the following example aggregate:
public class Order
{
private readonly IList<OrderLine> _orderLines;
public IEnumerable<OrderLine> Lines { get { return _orderLines; } }
public Order()
{
_orderLines = new List<OrderLine>();
}
public void AddOrderLine(string sku, int qty)
{
_orderLines.Add(new OrderLine(sku, qty));
}
public void CancelOrderLine(string sku)
{
OrderLine line = _orderLines.FirstOrDefault(l => l.Sku == sku);
if (line == null)
return;
line.Cancel();
}
}
public class OrderLine
{
public string Sku { get; private set; }
public int Qty { get; private set; }
public OrderLine(string sku, int qty)
{
Sku = sku;
Qty = qty;
}
public void Cancel()
{
// do some cancellation stuff
}
}
What is to prevent someone from going around the aggregate root and modifying the OrderLine directly? For example:
foreach(OrderLine line in currentOrder.Lines)
{
line.Cancel();
}
Is there anyway to have a truly encapsulated aggregate root? Would the only solution be to create a set of parallel order lines value objects that get exposed instead?
Thanks for any insight.
Can't you start with making the Cancel method internal so it is only visible inside your Order assembly?
Off course other classes in this assembly can then still access the cancel method.
Maybe another way is to expose the collection of OrderLines as a collection of IOrderLine interfaces and hide the Cancel method that way.
Why do you expose Lines? Expose some snapshot (DTO) containing information about OrderLines that you need outside but not OrderLines themselves. It's known solution. And with CQRS you may not need to expose it at all.
Which one is more preferred way to implement business object (and why)?
Without separate "context"
class Product
{
public string Code { get; set; }
public void Save()
{
using (IDataService service = IoC.GetInstance<IDataService>())
{
service.Save(this);
}
}
}
And usage would be:
Product p = new Product();
p.Code = "A1";
p.Save();
With separate "context"
class Product
{
private IContext context;
public Product(IContext context)
{
this.context = context;
}
public string Code { get; set; }
public void Save()
{
this.context.Save(this);
}
}
And usage would be:
using (IContext context = IoC.GetInstance<IContext>())
{
Product p = new Product(context);
p.Code = "A1";
p.Save();
}
This all is happening at BL layer (except usage examples), nothing to do with database etc. IDataService is interface to data layer to save business object "somewhere". IContext basically wraps IDataService somehow. Actual business objects are more complex with more properties and references to each other (like Order -> OrderRow <- Product).
My opinion is that first approach is (too) simple and second choice gives more control outside single business object instance....? Is there any guidelines for something like this?
I personally opt for a third version where the object itself does not know how to save itself, but instead relies on another component to save it. This becomes interesting when there are multiple ways to save an object, say saving it to a database, a json stream, an xml stream. Such objects are usually referred to as Serializers.
So in your case, I would go for as simple as this:
class Product
{
public string Code { get; set; }
}
a serialize for IContext serialization would be:
class ContextSerializer
{
public void SaveProduct(Product prod)
{
using(IContext context = IoC.GetInstance<IContext>())
{
context.Save(prod);
}
}
}
usage would be:
public void SaveNewProduct(string code)
{
var prod = new Product() { Code = code };
var contextSerializer = new ContextSerialzer();
contextSerializer.SaveProduct(prod);
}
This prevents the object from holding on to the context (the field in your example) and keeps your business objects simple. It also seperates concerns.
If you get into the situation where you have inheritance in your business objects, consider the Visitor Pattern.
I'm currently creating objects for an application of mine when this stuff come to mind. I know that using DBML's over Manual Creation of classes(see class below) can improve the speed of my application development but I'm really confused of what would be the other disadvantages and advantages of using DBML's over Manual Creation of classes like what I'm doing below thanks for all people who would help. :)
[Serializable]
public class Building
{
public Building()
{
LastEditDate = DateTime.Now.Date;
LastEditUser = GlobalData.CurrentUser.FirstName + " " + GlobalData.CurrentUser.LastName;
}
public int BuildingID { get; set; }
public string BuildingName { get; set; }
public bool IsActive { get; set; }
public DateTime LastEditDate { get; set; }
public string LastEditUser { get; set; }
public static bool CheckIfBuildingNameExists(string buildingName, int buildingID = 0)
{
return BuildingsDA.CheckIfBuildingNameExists(buildingName, buildingID);
}
public static Building CreateTwin(Building building)
{
return CloningUtility.DeepCloner.CreateDeepClone(building);
}
public static List<Building> GetBuildingList()
{
return BuildingsDA.GetBuildingList();
}
public static List<Building> GetBuildingList(bool flag)
{
return BuildingsDA.GetBuildingList(flag).ToList();
}
public static Building SelectBuildingRecord(int buildingId)
{
return BuildingsDA.SelectBuilding(buildingId);
}
public static void InsertBuildingRecord(Building building)
{
BuildingsDA.InsertBuilding(building);
}
public static void UpdateBuildingRecord(Building building)
{
BuildingsDA.UpdateBuilding(building);
}
public static void DeleteBuildingRecord(int building)
{
BuildingsDA.DeleteBuilding(building);
}
}
and my DAL is like this:
internal static class BuildingsDA
{
internal static Building SelectBuilding(int buildingId)
{
SqlCommand commBuildingSelector = ConnectionManager.MainConnection.CreateCommand();
commBuildingSelector.CommandType = CommandType.StoredProcedure;
commBuildingSelector.CommandText = "Rooms.asp_RMS_Building_Select";
commBuildingSelector.Parameters.AddWithValue("BuildingID", buildingId);
SqlDataReader dreadBuilding = commBuildingSelector.ExecuteReader();
if (dreadBuilding.HasRows)
{
dreadBuilding.Read();
Building building = new Building();
building.BuildingID = int.Parse(dreadBuilding.GetValue(0).ToString());
building.BuildingName = dreadBuilding.GetValue(1).ToString();
building.IsActive = dreadBuilding.GetValue(2).ToString() == "Active";
building.LastEditDate = dreadBuilding.GetValue(3).ToString() != string.Empty ? DateTime.Parse(dreadBuilding.GetValue(3).ToString()) : DateTime.MinValue;
building.LastEditUser = dreadBuilding.GetValue(4).ToString();
dreadBuilding.Close();
return building;
}
dreadBuilding.Close();
return null;
}
....................
}
I would also want to know if what could be the faster between the two methods of OOP implementation thanks :)
DBML
Pros:
You can get your job done fast!
Cons:
You can't shape your entity the way you want, for example you need 5 columns from the table but it has 10 columns you will get all of them, at least its schema. If you don't care much about data volum
You client side will have dependency with DAL (Data Access Layer), if you change property name, type in DAL you need to change in both BLL (Business Logic Layer) and client (Presentation Layer)
If you manual create class you might take a little bit more time to code but you get more flexible with it. Your client code will not depend on your DAL, any changes on DAL will not cause problems on client code.
Creating your model classes manually you can put additional attributes to properties (it cannot be done with DBML), apply your own data validation (as far as I remember it is possible to be done with DBML using partial methods).
With many tables and assocatiations DBML could become hard to read.
Disadventage of creating model classes manually is that you have to do all DBML stuff (attributes and a lot of code).
If you want to create model classes manually you can take a look at Entity Framework Code First or Fluent NHibernate. Both allows creating model easily.