I'm studying DDD at the same time that I'm reading some Khalil Stemmler posts and I'm a little confused about who should initialize the VO: the entity it is related to or the useCase that uses that entity. A simple example:
Let's assume that we are developing an internal purchase system for a company that has suppliers in several countries.
So the entity Supplier
may have properties like name: string
, country: Country
and taxId: ???
. The taxId
property has complicated business rules because when a Supplier
is from USA the validation rules from TaxId follows the TIN rules, but if the Supplier
is from Brazil the taxId validation rules follow the CNPJ rules. Because of this, it is necessary to have a Value Object for TaxId (which probably depends on Country to be instantiated) that deals with these complicated rules, right?
Then follows the central point of my doubt: When instantiating my entity Supplier
(in a useCase for example) I must pass as a parameter to the factory method a taxId: string
(raw format) and the factory instantiates the Value Object TaxID
(ie, if the TaxId is wrongly formatted I can't instantiate my Supplier
entity and I get a error message) or should I instantiate the TaxId
VO before and in case of success I pass taxId: TaxId
as the factory parameter?
1 Answer 1
A Supplier
factory that that calls a TaxId
VO factory is OK. Similarly a Supplier
factory that takes a TaxId
VO as parameter is also OK. However, avoid having the TaxId
validation in the Supplier
factory.
Depending on your use cases, you might need to be able to create a TaxId
VO without the Supplier
entity. If you have such use case, then a Supplier
factory that takes an existing TaxId
VO would be convenient.
By the way, you should not have an invalid TaxId
VO. Yet, the validity of the TaxId
VO depends on a Country
. Thus, I'd include Country
in the TaxId
VO (you would be passing the Country
to the TaxId
factory anyway).
-
I don't know if I understood what you meant in the last paragraph. How can I not have an invalid TaxId? If my supplier is from Brazil and TaxId is not a strictly numeric 14-character string then this value is invalid.Tâmer Cuba– Tâmer Cuba03/11/2021 18:13:02Commented Mar 11, 2021 at 18:13
-
1@TâmerCuba then the validation fails and you don't make the object. Edit: you are meant to put the data in terms that make sense for your system, and you are meant to take advantage of the type system. In that order of ideas, it makes sense to never create an invalid
TaxId
VO. If you do, then you need to call a method to check its validity in every place you need to use it. If you don't, then the type is enough to know it is valid.Theraot– Theraot03/11/2021 18:31:38Commented Mar 11, 2021 at 18:31 -
2@TâmerCuba Another way to say the same thing is: the idea is to make invalid tax id values unrepresentable within the core domain layer (or as close to unrepresentable as the language allows for). In other words, it's the best if you can arrange things so that if some code was able to create a TaxId instance, then you can assume it's valid. If, say, a TaxId constructor throws an exception, it should be an exceptional circumstance - in this case, a bug in your code. Now, outside of core domain, within the use cases (application logic), this is not necessarily the best strategy. 1/2Filip Milovanović– Filip Milovanović03/11/2021 19:35:03Commented Mar 11, 2021 at 19:35
-
2E.g. if the use case involves users entering tax id-s manually, an invalid tax id is not exceptional. It's a common scenario, and a part of the use case is to assist users with early feedback. So in the use case, you might work with a different representation of the tax id, and only convert it to the TaxId type once the user is ready to commit. You might want to share some of the tax id validation logic between the two, but differ in how strongly they respond to violation, so you might introduce a TaxIdStringValidator service object as part of the core domain that works on strings. 2/2Filip Milovanović– Filip Milovanović03/11/2021 19:35:06Commented Mar 11, 2021 at 19:35
-
I like to add the factory code in my Value Objects (factory method pattern), in C# it would be something like
public static TaxId ForCountry(Country country, string value)
Rik D– Rik D03/12/2021 10:44:07Commented Mar 12, 2021 at 10:44