@@MilanJovanovicTech I actually find it neat if you see the Ids as “guarded types or values” because you get validation wherever the values first get created, like in controllers.
@@MilanJovanovicTech I feel like if you don't do it _everywhere_ - to the point it's almost difficult to know what it's representing - it loses a lot of it's power
I came here to know more about CLEAN architecture tutorials but I guess I have to start watching first the DDD playlist. This really helps me a lot and will definitely give you a tip on my next payroll 💯
@@MilanJovanovicTech yes true it is! But for some people who are new to it they may want your experience with it. Yes we can add integration tests, unit tests , system tests etc but for a junior dev it’s a new concept.
I agree..."Strongly Typed Identities" make your method-parameters "more honest". And although it is cool...things like this often become overused and/or misused & I can see it quickly "blowing up" the amount of code for little (real) benefit. When implementing stuff like this, ask yourself... Q: Are we ACTUALLY EXPERIENCING the issue this approach solves? (probably not) Q: Does this approach increase or decrease complexity? (it increases it) Q: Does this approach increase or decrease the amount of conversion in-code? (it increases it) Q: Does it work seemlessly across every layer & technology we use? (nope) Q: Is there a simpler approach to make my method-parameters "more honest"? (clear parameter-names will do it well enough) Q: Can "clear naming" help ensure people pass the correct value? (yes) Q: Can "bad value" mistakes be caught in Unit Tests? (yes) Overall... Your code complexity increases & you will have to convert back-and-forth across MULTIPLE layers to solve a problem your not really having. As such... ATM, I would say while VERY COOL...the bang isn't worth the buck (as of yet)
You seconded a thought which did cross my mind, and you did it quite descriptively. However, I do have a counter-argument. If the domain is not too complex, this might be an overkill. But for a large and complex domain with lots of entities, maybe this would be more helpful for clear understanding and maintainability. For example, the LineItems example shown in the video which has numerous Guid in constructor. There, this approach makes more sense especially when you need to simply change the order of arguments.
The one change (or addition) I would implement here is to add a static Create method to the Id types, thus hiding the internal implementation of the Id from the Customer object. ie. public static CustomerId Create() => new CustomerId(Guid.NewGuid());
Only from strongly typed ID to primitive type should be ok, but the reversed would bring back the problem of missplacing methods parameters, since primitives will be accepted due to implicit conversion
There is a nugget package cold StronglyTypedId that allows implement this concept but it is based od structs. Records are under the hood a classes and they are allocated on the heap. And there is the question, which aproach is better and more effective ?
I guess that would be good continue this video with another one showing about how to use this type as a original type with converters and serializers on controllers and EF Core. How to allow to use it as route parameters.
Yeah, that video is coming out next Friday. Trying to keep the videos bite-sized and only focus on one small topic. Next Friday's video will cover mapping all the DDD patterns with EF Core.
I like how strongly typed id's can add so much more information rather than a property name and a primitive value. However, I am worried that this can cause much more complication when trying to generalize common code.. Like for example creating a base repository IRepository since the type of id is no longer abstractable.. You'd have to add another type parameter now I think.. like IRepository.. I don't know if I like that.. I have a bunch of boiler plate persistence libraries coded around an integer id.. and since I always name my Id's "Id" and I always know the type "int" then I can abstract those properties to an interface or base class. But I don't think you can really do that with strongly typed Ids can you?
I think you will have to do like this public interface IRepository where TEntity : IEntity { TEntity GetById(TId id); void Add(TEntity entity); void Update(TEntity entity); void Remove(TEntity entity); } public interface IEntity { TId Id { get; } }
Two questions: 1. Wouldn‘t it be better to use record structs in your example? The strong typed id would behave like a value type in this case. 2. If you implement implict cast operators in the record. Would EF be able to use the strong typed id ,when writing/reading to/from the DB?
1. Maybe - are there other implications with using a `value` type in terms of copying? 2. I do believe so, yes! But it's simpler to use a value conversion
The major advantage I can see to value objects is that let's say I have an identifier that is integer for customer id. If I decided that int is not big enough and decided to change the type to GUID then I can do that pretty much in one spot.. And everywhere that it is referenced gets that update. So maintainability and scalability is enhanced.. correct?
I like the idea, enough benefits to use it - another advantage you could argue is that you can change the data type of the ID with minimal code description, or at least less stuff to refactor. Or the most practical use I can think of is that you can support multiple I’d types at the same time out of the box. In the past I defined them as value objects but record is waaay nicer. Value converters are pretty east in EF core but yeah it would be good if you show ppl few examples, might make them a bit more comfortable implementing ST ids
Changing the data type will probably be a headache at the database level, so it's frail at best. I don't see why so much people hate this approach however.
Everything is explained perfectly but I still got some issues which indicating that I don't have primary key for PostId, I'm building blog where Post has strongly typed id PostId, which is record, any suggestions ?
If I have aggregate root and it gives id to my domain entity then how to implement strongly typed IDs . For example public class Product : AggregateRoot { }
Insightful, thanks Milan! Although I learned something I wouldn't go with the strongly typed Ids. Why do you see ID as a complex type? Instead of primitive obsession you're getting class explosion plus all disadvantages can be easily solved and we wouldn't get the disadvantages that you mentioned. Disadvantages that you solved using strongly typed IDs: - In the LineItem example, when creating a line item and passing parameters, you might pass them in the wrong order: I see other problem of not using named parameters (or even in a combination with the positional ones) which can improve code readability and protect you from such problems. new LineItem(id: Guid.NewGuid(), orderId: Id, productId, product.Price) -In the Repository when getting Customer by Id Task GetByIdAsync(Guid id) Since it's a customer repository it by convention should accept customerId. If you accept productId your singature would (should) look different as well. Task GetByProductIdAsync(Guid id) Again this is my perspective, Thanks for sharing!
Thank you, I have one simple question: wouldn't be best to use a record struct instead of record, considering that primitive type GUID is a readonly struct?
Which layers would you expose your Typed ID to? For example would you make the command contracts in the Application layer use the typed ids? so the UI calling the commands would need to know about the typed ids? or would you just use a GUID and convert them in the command?
I am wondering if this approach can stil be combined with inheritance from an abstract Entity class to get all the equality stuff back. E.g. a CustomGuid an then an Entity
Use a data transfer object DTO. When writing to the database, convert the domain model to a DTO and use that. Sinilarly when reading from the database, read into a DTO and then convert that to a domain model. It gives a cleaner separation between domain and the database. It also helps when you domain model is constructed from multiple tables on the database.
Strongly typed IDs can make your code more expressive and improve the maintainability and correctness of your Domain-Driven Design (DDD) applications. In DDD, it's essential to model your domain entities accurately, and using strongly typed IDs is a technique that aligns well with this principle
Hi Milan, one of the things you said about the strongly typed IDs is that they are immutable but in the case of the line item, what if you had saved a line item with a strongly typed product Id but later find that it was not the correct product and need to change it. Would that cause a problem if you need to change the product Id in the line item when it is supposed to be immutable?
Under this situation, you wouldn't change the Product in the Line Item, rather, you would remove the Line Item from the Order (or better yet set a flag on the Line Item) and add a corrected Line Item. This allows for audit tracking of the Order.
The ProductId itself is immutable - as an object. But on the LineItem, you can simply expose a method like ChangeProduct(ProductId) and replace the Product. Or simply create a new LineItem. Think about how you would fill your own shopping cart in the store? You remove something from your bag. You add something else.
Fantastic Content I am thinking of using a strongly typed id for a multi-tenant applicaiton which will result in many id's being composite for instance a CustomerId will consist of a guid for customer id and guid for tenant id. My question is, is this a good approach to solve such a problem secondly how would you map such a key on EF Core
How do you approach referencing aggregate roots by other aggregate roots? As far as I know, we should avoid defining relationships between the aggregates, because we don't want changes in aggregate A to affect aggregate B. However, there's a problem in EF Core (Amichai recorded a video about it) that makes it difficult to create one-many relationship, because for example the ProductId in product aggregate should be a value object, but List in order aggregate should be entity type, to create table order_product_ids. Do you have some other way to do this? Because I don't like the Amichai's approach (although it's the best I've found)
@@MilanJovanovicTech That was just an example where an aggregate has references to a list of other aggregate, I'm talking about the problem described here (ruclips.net/video/B3Iq346KwUQ/видео.html) by Amichai. Do you solve that differently, or just use ef core relations? Amichai said that we should avoid ef core relations between aggregates, so that's why I'm asking.
Strongly typing IDs (and other primitives that could be expressed as some sort of value objects) is a wonderful idea. However, problems or goofy decisions appears when time comes for those IDs to be serialized in one way or the other. How would you save those IDs in database? And, how would you deal with serialization if strongly typed id is part of DTO which is sent and received from front end?
That is also my concern. It will be somewhat cumbersome when working with a database or serialization. Imagine the JSON of it: "customerId": { id: "db7df04d-8d9d-4966-8949-f6638dc883eb" } and not only "customerId": "db7df04d-8d9d-4966-8949-f6638dc883eb"
Highly recommend StronglyTypedIds nuget package. It uses Source Generators and gives you Json serializer out of the box. Also when working with EF it is easy to add a convention that saves the Id in the database the proper way
You wouldn't use the strongly typed ID in DTOs, they exlusively live in the core layer, i.e. the domain. You also wouldn't reuse domain objects as DTOs. That said, you might need to do an object-mapping mechanism. Well known tools like, e.g. AutoMapper, support good ol' TypeConverters for such things.
When the primary key is composed of two primitive properties, creating a strongly-typed ID becomes problematic in the mapping with EFCore (even 8). Have you ever had to deal with this case? PS You're a great professional; since I started following you, I've started thinking differently.
And then the business comes and ask you why that feature you were supposed to deliver in 3SP just went up to 8SP. All of this only because we tend to overenginer for the wrong reasons
@@MilanJovanovicTech I worked on a project which heavily used DDD. Now the project has over 3 mil lines of code. As this was some kind of modular monolith, we started to split it. The big bad wolf was that this project abused the DDD principles in the early days. The team worked mostly on features, over time we noticed (as a group) that we lost knowledge of some DDD practices from the early days, and even worse we started having issues with mapping POCOs because of situations like this one (that's what happens when you pick a "very" popular database engine). To add the cherry on top - everything was extremely configurable and every change could break the domain and business logic. So this was not the only cause but in the end, it was contributing to the huge increase in the expected work. So your 3 SP just got to 8 SP overnight... Even I agree with you on this one, if I would have a time machine and could rewind the time I would decide to keep the domain under KISS & YAGNY and add this kind of DDD practice under "I do not need it" - it's the app core and not a place where you dump anything you found on the internet or all the new business requirements.
@@tryagain6148 But it does look like a lot more went wrong on this project - and the blame is probably on the engineers (which is typically the case) and not with DDD or any well accepted practice
I like use abstract class entity and create my id in this class with anothers comuns properties like createdAt, updatedAt. In this case i can use strong type for my ids?
@@MilanJovanovicTech Hello. Could you recommend me the decision for case above? I've started to use ST Ids into my pet project. However I've met an issue with this approach. Imagine: There is an hierarchy of classes. Driver - RideParticipant - User - AggregateRoot And Passenger - RideParticipant - User - AggregateRoot. I've used Guid before, but now it would be great if I'll have ids DriverId and PassengerId. All other are abstract classes and in many businesses cases an inheritance could be very huge (for a well designed too). So, there is a problem. If I want to have it, I need to make abstract classes as generic. However I want to use them as non-generic types, because there are classes that applies that abstractions and I don't want to make everything generic recursively. What can you suggest?
Does this remove the need for an id property a base entity class then? Because each entity will have a specific id type. I've been going over your pragmatic clean architecture course, and in there, you have a base class for entities that takes a guid id for the constructor. But if that base property gets removed, i lose the safety of making sure each entity has an id defined. I tried making the entity bass generic with a TId, but it seems to be getting out of hand fast.
Thank You Millan . I have a question If I have a Person entity and a Customer entity and the Customer entity inherits the Person entity How do I use Strongly Typed ID In this case
For example, if you create a strongly typed id for the person entity and another for the customer entity, but since the customer entity inherits the person entity, it certainly gets the person entity id. How do we solve this problem?
Would it be an idea to call new guid by default inside the default constructor of a strongly typed id so that new guid isn't called outside the new record. It Could still make muddle up if you write new LineItem(new LineItemId(product.Id.Value).....), even though that seems ridiculous 😅😅
On one hand I am a big fan of strongly typed entity identifiers, but i really wish this demo used record structs instead of just records. In the domain model, identifiers are almost always conceptualized with value-type semantics and not reference-type semantics. I think turning a primitive type identifier into a domain-specific *reference* type identifier throws the baby (value-type semantics) out with the bathwater and ends up turning lists of identifiers from something fast, lightweight and space efficient in the primitive obsessed world, into something that ends up being heap allocated for every identifier. I think part of the reason for primitive obsession is the simplicity and performance. With record structs you get it all - a clean type-safe domain model AND all the safe performance and space-savings of value types.
@@MilanJovanovicTech I use a readonly struct type to model these domain ids and implement overrides for equals, operators, hashcode and casting operators to streamline conversion back and forth to primitive types for DTO's to the database
Yo, Milan. Did you ever get an exception while creating dbContext called "Unable to create a 'DbContext' of type ' '.The entity type 'ExampleEntityId' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'"? Even though I have specified this entity as the primary key of my class using fluent api. Can't find a solution.
@@MilanJovanovicTech Yes i did: builder.Property(p => p.Id).HasConversion( categoryId => categoryId.Value, value => new CategoryId(value)); that's why i find this exception weird.
@@MilanJovanovicTech I fixed the exception. It consisted in the fact that I incorrectly configured the foreign key for an entity in another class. How was it: builder.HasOne(x => x.CategoryId) .With Many() .HasForeignKey(x => x.Id); How to do it correctly: builder.HasOne() .With Many() .HasForeignKey(x => x.CategoryId);
This doesn't always work, for instance with composite keys. Tables Tenant(TenantId) and TableA(TenantId, AId). Now TableB(TenantId, BId) wants to link to TableA with extra field (AId) so TableB(TenantId, BId, AId). The TenantId is now shared; if you don't share the key you risk links to data belonging to another tenant! If you use a strongly typed id then the TenantId gets duplicated, is redundant and can still point to another tenant.
@@MilanJovanovicTech If you use strongly typed ids, then TableB would be (TenantId, BId, TenantId2, AId) - first two fields are the key for TableB, second two are the key reference to TableA; now there is nothing stopping you from linking an entry in TableB to an entry in TableA that belongs to a different tenant.
Another very useful DDD concept that I've learned from @MilanJovanovicTech is the use of value objects. But I'm a little unclear as to the difference between the two. Would a value object not have been just as suitable, or better, to use for the ID? Would anyone care to explain the difference?
Records are immutable, so you can consider this a value object. Don't think about a value object as "it inherits from a class". Think about it from the qualities it has: immutable, represented by its value.
@@Kingside88 The real benefit is that you can't accidently transpose parameters when calling methods. Database logic isn't a problem for us as we never pass the domain object directly to EF Core. We always create a data transfer object (DTO) from the domain object and use that. Similarly, when reading from the database, we reading into a DTO and then create the domain object from the DTO.
The only benefit I see is not being able to the use the identity where you’re not supposed to. Example, using a UserId to get an Office. Obviously doing this would result in a bug, but with this solution, it would cause a design-time error.
@@jpsytaccount Yes - and that's a massive benefit. The quicker you pick up these mistakes the cheaper it is to fix it. You'd like to think that unit testing and QA will pick up these mistakes, but in the real world, that doesn't always happen. Even if they do, the cheapest bug to fix is still the one that the compiler detects..
Others have chipped in with some pretty good benefits. I like it because it makes everything more expressive with strong typing. Of course if you feel this is overkill, no need to use it. I don't like to be dogmatic about any design pattern, and leave it up to the developer to pick and choose what to use.
Great video as usual. But, when using Records this way, you are taking a performance and memory hit. Based on your use, it may be better to use a struct with IEquatable as shown in the example below. public struct CustomerId : IEquatable { public CustomerId(Guid value) => Value = value; public Guid Value { get; } public bool Equals(CustomerId other) => Value.Equals(other.Value); public override bool Equals(object? obj) => obj is CustomerId other && Equals(other); public override int GetHashCode() => Value.GetHashCode(); public static bool operator ==(CustomerId left, CustomerId right) => left.Equals(right); public static bool operator !=(CustomerId left, CustomerId right) => !left.Equals(right); public override string ToString() => Value.ToString(); }
@@MilanJovanovicTech, it is a little verbose. It would be a mater of deciding if being a little verbose with worth not taking the performance and memory hits for using Records. I believe Records use reflection under the hood and that could be why there is that hit.
Hey Mate, great video :) Could you make one on how to define a composite key for a join table? I.e. Key is two foreign keys to entities a & b. I've got a requirement for a join table between two entities that also has extra info associated with it. Not entirely sure how to implement that with DDD
Usage of strongly typed ids for Guid, int or long just misunderstood. If you have strongly typed ids you should define custom business identifier instead of database id. Let's say you have TrackingId for your shipment, that TrackingId should be generated in domain layer with logic.
Good work Milan!, my Question is out of this topic. Note:- My english isn't good enough 😅 i hope you will get the point? we use Automapper to map mostly DTO & DB models, which saves lines of code for assigning. Question is if we use mapper inside .Select(s => _mapper(s)) during fetching data using Efcore then it maps on client site but i want to map data on server side, how to handle automapper to map data on server side.
Hello Milan, I was looking for your email but it seems you already answer questions in the comments and I appreciate that. Recently I have a question and the first person that came to my mind to ask it was you. In a book I was reading, it suggested to ask someone who has gone the path you want to go, the question of how they think about their experiences and whether the effort was worth it to get there. As a software engineer, how would you go about this question? I respect what you’re doing, but I imagine it has high points and low points. Could you share them with me? Knowing what you know now, is it worth the effort?
Anything worth achieving is always worth the effort :) Sounds a bit cliche, but it's true. Was it hard getting to where I'm at? Heck yeah. But I don't regret it. The highs are awesome, and the lows can be painful. What I try is to keep moving "forward" each day, at least a little. Wherever "forward" is (you have to decide for yourself). Not sure if this answers your question.
Oh boi, this is really confusing I though in your recent video you said the entities could be within Infrastructure but now you are using in the Domain project; haaah haha. My name is also true for coding, it seems.
@@MilanJovanovicTech You are right, sorry I was thinking about the repository interface; it's a tiny bit complicated because there are a lot of data to take in. In mean timetime watched more of your video and there seems to be multiple ways, so it's not strange anymore. 😅
Thanks for the link. Nick's video does give some much needed context with dto/API and domain and doesn't wrap a struct in a class. Still think this is borderline over engineering, but seeing the API to domain conversion and serialisation makes me think there is a good argument for these.
This is what is known as over-engineering and there is more code with barely any real world benefit. In your last example with the repository get by ID method you already know that you need the ID itself because the method is called "GetByID" so the code will typically read "myThingRepo.GetByID" and it should be returning a singular of . If someone can't work that out then they shouldn't be using a text editor. Also I've seen primitive obsession where you have long list parameters where everything is a string or int. A struct / class is the obvious replacement. What you are presenting here isn't really primitive obsession. Is really passing a Guid to a clearly labelled method really primitive obsession? The only example I think where it might be beneficial is in a constructor example with many IDs. Even then it like marginal benefit. You could just use a class with clearly labelled IDs.
I agree, this seems to just add complexity where there is no need. The agrument about the method signature being strongly typed seems a little silly, well what if I can that code an create a new productId instead of passing the productId, this feels just as stupid as not using the correct ordering (the argument names do serve a purpose). I feel like videos like these mislead beginners into over engineering.
I'm not convinced for the need of strongly type Ids. They look cool, but they have very little to none practical usefulness. There's not much primitive obsession in this case because there's not much to encapsulate in my opinion.
There's no need for it, sure. What I did wrong in this video is not using a better example for the ID. Instead of just wrapping a primitive type, I should've shown a "meaningful ID". I'll make a post about it. In general, strongly typed IDs _can_ bring some qualities that you may find useful, so that's when you should consider using it.
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
you truly stand out differently by giving practical examples..and during the course we got to know many other new things as side effect 😊
Wow, thanks a lot Rahul! This made my day :)
Love it. Could you do a follow up showing exactly how to integrate it with efcore…. That would be great.
Yes, the EF Core + DDD Mapping video is coming out next Friday :)
@@MilanJovanovicTech Great! I'm looking forward to it.
A tip: You can implement IParsable so you can use these strongly typed Ids for arg in your controllers and endpoints.
I would honestly leave them out of the controller endpoints and just use primitive types, but that's just my opinion
@@MilanJovanovicTech I actually find it neat if you see the Ids as “guarded types or values” because you get validation wherever the values first get created, like in controllers.
@@MilanJovanovicTech I feel like if you don't do it _everywhere_ - to the point it's almost difficult to know what it's representing - it loses a lot of it's power
I came here to know more about CLEAN architecture tutorials but I guess I have to start watching first the DDD playlist. This really helps me a lot and will definitely give you a tip on my next payroll 💯
Welcome aboard, Pete 😁 Glad you found the channel
Love your videos keep up with them! I be keen to see your take on testing strategies within your DDD projects.
Unit testing DDD is pretty easy, don't you think?
@@MilanJovanovicTech yes true it is! But for some people who are new to it they may want your experience with it. Yes we can add integration tests, unit tests , system tests etc but for a junior dev it’s a new concept.
I like the approach and in our current project we actually apply it. With EF Core it is also flawless to be persisted.
Is there anything I could have added in the video?
I agree..."Strongly Typed Identities" make your method-parameters "more honest".
And although it is cool...things like this often become overused and/or misused & I can see it quickly "blowing up" the amount of code for little (real) benefit.
When implementing stuff like this, ask yourself...
Q: Are we ACTUALLY EXPERIENCING the issue this approach solves? (probably not)
Q: Does this approach increase or decrease complexity? (it increases it)
Q: Does this approach increase or decrease the amount of conversion in-code? (it increases it)
Q: Does it work seemlessly across every layer & technology we use? (nope)
Q: Is there a simpler approach to make my method-parameters "more honest"? (clear parameter-names will do it well enough)
Q: Can "clear naming" help ensure people pass the correct value? (yes)
Q: Can "bad value" mistakes be caught in Unit Tests? (yes)
Overall...
Your code complexity increases & you will have to convert back-and-forth across MULTIPLE layers to solve a problem your not really having.
As such...
ATM, I would say while VERY COOL...the bang isn't worth the buck (as of yet)
This is a valuable perspective to have
Great presentation. I'm just starting a new project and I'm going to use strongly typed IDs, for sure.
Good luck and be careful of overengineering
You seconded a thought which did cross my mind, and you did it quite descriptively.
However, I do have a counter-argument. If the domain is not too complex, this might be an overkill. But for a large and complex domain with lots of entities, maybe this would be more helpful for clear understanding and maintainability.
For example, the LineItems example shown in the video which has numerous Guid in constructor. There, this approach makes more sense especially when you need to simply change the order of arguments.
The one change (or addition) I would implement here is to add a static Create method to the Id types, thus hiding the internal implementation of the Id from the Customer object.
ie. public static CustomerId Create() => new CustomerId(Guid.NewGuid());
I agree that would be a good solution. But you would also end up writing more code. I guess it's an okay tradeoff to get an even cleaner design.
What about casting operator ( Guid => ProductId) in the record itself?
@@andreasmewald2439 Using an implicit operator could work.
public static implicit operator Guid(CustomerId value) => value.Value;
@@JohnOliverAtHome it‘s less noisy and if you‘re using rider, rider will show you the implicit convertion in the inlay hints
Only from strongly typed ID to primitive type should be ok, but the reversed would bring back the problem of missplacing methods parameters, since primitives will be accepted due to implicit conversion
There is a nugget package cold StronglyTypedId that allows implement this concept but it is based od structs.
Records are under the hood a classes and they are allocated on the heap. And there is the question, which aproach is better and more effective ?
It depends, as often. You can, however, choose between "record [class]" and "record struct" in C#.
I think it's cherry picking, but I'm not as keen on micro-optimizations.
I guess that would be good continue this video with another one showing about how to use this type as a original type with converters and serializers on controllers and EF Core. How to allow to use it as route parameters.
Yeah, that video is coming out next Friday. Trying to keep the videos bite-sized and only focus on one small topic. Next Friday's video will cover mapping all the DDD patterns with EF Core.
I like how strongly typed id's can add so much more information rather than a property name and a primitive value. However, I am worried that this can cause much more complication when trying to generalize common code.. Like for example creating a base repository IRepository since the type of id is no longer abstractable.. You'd have to add another type parameter now I think.. like IRepository.. I don't know if I like that.. I have a bunch of boiler plate persistence libraries coded around an integer id.. and since I always name my Id's "Id" and I always know the type "int" then I can abstract those properties to an interface or base class. But I don't think you can really do that with strongly typed Ids can you?
As you suspect, it introduces a lot of boilerplate if you want to generalize around it. Win some, lose some - right?
I think you will have to do like this
public interface IRepository where TEntity : IEntity
{
TEntity GetById(TId id);
void Add(TEntity entity);
void Update(TEntity entity);
void Remove(TEntity entity);
}
public interface IEntity
{
TId Id { get; }
}
Why not use readonly struct record? Why should I allocate one more extra object on the heap?
If you care about that extra object, go with a struct
Two questions:
1. Wouldn‘t it be better to use record structs in your example? The strong typed id would behave like a value type in this case.
2. If you implement implict cast operators in the record. Would EF be able to use the strong typed id ,when writing/reading to/from the DB?
1. Maybe - are there other implications with using a `value` type in terms of copying?
2. I do believe so, yes! But it's simpler to use a value conversion
A record doesn't enforce immutability. It is syntactical sugar for class + ctor + init-property.
You can have mutable properties in record.
It'd better to use record struct for immutability.
If you try hard enough nothing is immutable :)
A record define like in the video is immutable.
The major advantage I can see to value objects is that let's say I have an identifier that is integer for customer id. If I decided that int is not big enough and decided to change the type to GUID then I can do that pretty much in one spot.. And everywhere that it is referenced gets that update. So maintainability and scalability is enhanced.. correct?
Yes - but what about the code creating that strongly typed ID? It'll also need to be updated
I like the idea, enough benefits to use it - another advantage you could argue is that you can change the data type of the ID with minimal code description, or at least less stuff to refactor. Or the most practical use I can think of is that you can support multiple I’d types at the same time out of the box. In the past I defined them as value objects but record is waaay nicer. Value converters are pretty east in EF core but yeah it would be good if you show ppl few examples, might make them a bit more comfortable implementing ST ids
Changing the data type will probably be a headache at the database level, so it's frail at best. I don't see why so much people hate this approach however.
@@MilanJovanovicTech these days developing software requires a rather high degree of being open minded
Great stuff.
Have you even thought about doing mini tutorials like this for frameworks like ABP or something similar ?
I don't think covering frameworks makes sense if I didn't use them in Production
@@MilanJovanovicTech Ok, got it. It would be quite interesting to see your take on some frameworks that are built with DDD in mind like ABP.
Why would you place the id record on a different file? Why not before the class where you’re using it?
I (almost) never place two classes in the same file
Everything is explained perfectly but I still got some issues which indicating that I don't have primary key for PostId, I'm building blog where Post has strongly typed id PostId, which is record, any suggestions ?
Can't say much without seeing your code
Great!
one question can I type an integer (in the db it will become incremental) instead of a GUID?
I think it should work, but you should test that first
If I have aggregate root and it gives id to my domain entity then how to implement strongly typed IDs . For example
public class Product : AggregateRoot { }
I'll try it out and see, but something like AggregateRoot should be possible
I have the same question....coulnd't get it to work with AggregateRoot
Insightful, thanks Milan!
Although I learned something I wouldn't go with the strongly typed Ids.
Why do you see ID as a complex type?
Instead of primitive obsession you're getting class explosion plus all disadvantages can be easily solved and we wouldn't get the disadvantages that you mentioned.
Disadvantages that you solved using strongly typed IDs:
- In the LineItem example, when creating a line item and passing parameters, you might pass them in the wrong order:
I see other problem of not using named parameters (or even in a combination with the positional ones) which can improve code readability and protect you from such problems.
new LineItem(id: Guid.NewGuid(), orderId: Id, productId, product.Price)
-In the Repository when getting Customer by Id
Task GetByIdAsync(Guid id)
Since it's a customer repository it by convention should accept customerId. If you accept productId your singature would (should) look different as well.
Task GetByProductIdAsync(Guid id)
Again this is my perspective,
Thanks for sharing!
I appreciate all perspectives. I think it's a matter of finding a bug at design time vs runtime. Which do you prefer?
@@MilanJovanovicTech well in that question you left me no choice 😅
@@MilanJovanovicTech Are you saying that your Unit Testing of the Domain would NOT find a bug caused by mistaken argument transposition?
Thank you, I have one simple question: wouldn't be best to use a record struct instead of record, considering that primitive type GUID is a readonly struct?
Yes
Which layers would you expose your Typed ID to? For example would you make the command contracts in the Application layer use the typed ids? so the UI calling the commands would need to know about the typed ids? or would you just use a GUID and convert them in the command?
Both can work, as the responsibility is the same. I'd probably do it in the handlers though
I am wondering if this approach can stil be combined with inheritance from an abstract Entity class to get all the equality stuff back. E.g. a CustomGuid an then an Entity
Yes, but it becomes messy pretty fast
Thanks as always, Milan! How would you go on to implement the EF conversions for read and write?
Use a data transfer object DTO. When writing to the database, convert the domain model to a DTO and use that. Sinilarly when reading from the database, read into a DTO and then convert that to a domain model. It gives a cleaner separation between domain and the database. It also helps when you domain model is constructed from multiple tables on the database.
Coming out in Tuesday's video
Strongly typed IDs can make your code more expressive and improve the maintainability and correctness of your Domain-Driven Design (DDD) applications. In DDD, it's essential to model your domain entities accurately, and using strongly typed IDs is a technique that aligns well with this principle
✅
Hi Milan, one of the things you said about the strongly typed IDs is that they are immutable but in the case of the line item, what if you had saved a line item with a strongly typed product Id but later find that it was not the correct product and need to change it. Would that cause a problem if you need to change the product Id in the line item when it is supposed to be immutable?
Under this situation, you wouldn't change the Product in the Line Item, rather, you would remove the Line Item from the Order (or better yet set a flag on the Line Item) and add a corrected Line Item. This allows for audit tracking of the Order.
The ProductId itself is immutable - as an object. But on the LineItem, you can simply expose a method like ChangeProduct(ProductId) and replace the Product. Or simply create a new LineItem. Think about how you would fill your own shopping cart in the store?
You remove something from your bag. You add something else.
Fantastic Content
I am thinking of using a strongly typed id for a multi-tenant applicaiton which will result in many id's being composite for instance a CustomerId will consist of a guid for customer id and guid for tenant id. My question is, is this a good approach to solve such a problem secondly how would you map such a key on EF Core
I'm not sure about the mapping. A value converter maybe. But I suggest you test the query support.
How do you approach referencing aggregate roots by other aggregate roots? As far as I know, we should avoid defining relationships between the aggregates, because we don't want changes in aggregate A to affect aggregate B. However, there's a problem in EF Core (Amichai recorded a video about it) that makes it difficult to create one-many relationship, because for example the ProductId in product aggregate should be a value object, but List in order aggregate should be entity type, to create table order_product_ids. Do you have some other way to do this? Because I don't like the Amichai's approach (although it's the best I've found)
Why would an Order have a List however?
Wouldn't it have Order -> List and then LineItem -> ProductId?
@@MilanJovanovicTech That was just an example where an aggregate has references to a list of other aggregate, I'm talking about the problem described here (ruclips.net/video/B3Iq346KwUQ/видео.html) by Amichai. Do you solve that differently, or just use ef core relations? Amichai said that we should avoid ef core relations between aggregates, so that's why I'm asking.
Strongly typing IDs (and other primitives that could be expressed as some sort of value objects) is a wonderful idea. However, problems or goofy decisions appears when time comes for those IDs to be serialized in one way or the other. How would you save those IDs in database? And, how would you deal with serialization if strongly typed id is part of DTO which is sent and received from front end?
That is also my concern. It will be somewhat cumbersome when working with a database or serialization. Imagine the JSON of it: "customerId": { id: "db7df04d-8d9d-4966-8949-f6638dc883eb" } and not only "customerId": "db7df04d-8d9d-4966-8949-f6638dc883eb"
Highly recommend StronglyTypedIds nuget package. It uses Source Generators and gives you Json serializer out of the box. Also when working with EF it is easy to add a convention that saves the Id in the database the proper way
It concerns me anyone would call this a "wonderful idea".
Just serialize the raw value at the DB value. Not the object itself.
You wouldn't use the strongly typed ID in DTOs, they exlusively live in the core layer, i.e. the domain. You also wouldn't reuse domain objects as DTOs. That said, you might need to do an object-mapping mechanism. Well known tools like, e.g. AutoMapper, support good ol' TypeConverters for such things.
What about record struct?
Viable option - what about passing it around?
That is great, but how can use these strongly types ids with Entity Framework?
Showing that in Friday's video
@@MilanJovanovicTech
When the primary key is composed of two primitive properties, creating a strongly-typed ID becomes problematic in the mapping with EFCore (even 8). Have you ever had to deal with this case?
PS You're a great professional; since I started following you, I've started thinking differently.
It's too problematic, probably want to avoid it with composite keys
Its great idea for a global reactor of a big project)) have been wanting to implement this for a long time)
Maybe too much for a global refactor 😅
What about creating a CustomerId struct that simply inherits from Guid. Or is that impossible?
Isn't inheritance not supported with structs?
@@MilanJovanovicTech Oh, ok. The thought just came to me whole watching the video, nevermind then.
And then the business comes and ask you why that feature you were supposed to deliver in 3SP just went up to 8SP. All of this only because we tend to overenginer for the wrong reasons
This won't add 5 SP don't worry
@@MilanJovanovicTech I worked on a project which heavily used DDD. Now the project has over 3 mil lines of code. As this was some kind of modular monolith, we started to split it. The big bad wolf was that this project abused the DDD principles in the early days. The team worked mostly on features, over time we noticed (as a group) that we lost knowledge of some DDD practices from the early days, and even worse we started having issues with mapping POCOs because of situations like this one (that's what happens when you pick a "very" popular database engine). To add the cherry on top - everything was extremely configurable and every change could break the domain and business logic.
So this was not the only cause but in the end, it was contributing to the huge increase in the expected work. So your 3 SP just got to 8 SP overnight...
Even I agree with you on this one, if I would have a time machine and could rewind the time I would decide to keep the domain under KISS & YAGNY and add this kind of DDD practice under "I do not need it" - it's the app core and not a place where you dump anything you found on the internet or all the new business requirements.
@@tryagain6148 But it does look like a lot more went wrong on this project - and the blame is probably on the engineers (which is typically the case) and not with DDD or any well accepted practice
There is an issue using strongly typed ids. Npgsql provider for EF Core doesn't support them as well as SQL provider.
Oh really? Well the next video is how to configure it with EF Core
@@MilanJovanovicTech try to configure it using HiLo. Also you can check issue #2617 in Npgsql provider for PostgreSQL repository
@@MilanJovanovicTech I was goin to ask if you will do video about configuring it, but found the answer here :D
I like use abstract class entity and create my id in this class with anothers comuns properties like createdAt, updatedAt. In this case i can use strong type for my ids?
Yeah, but you'll have to make Entity class generic
@@MilanJovanovicTech Hello. Could you recommend me the decision for case above? I've started to use ST Ids into my pet project. However I've met an issue with this approach.
Imagine: There is an hierarchy of classes.
Driver - RideParticipant - User - AggregateRoot
And
Passenger - RideParticipant - User - AggregateRoot.
I've used Guid before, but now it would be great if I'll have ids DriverId and PassengerId. All other are abstract classes and in many businesses cases an inheritance could be very huge (for a well designed too).
So, there is a problem. If I want to have it, I need to make abstract classes as generic. However I want to use them as non-generic types, because there are classes that applies that abstractions and I don't want to make everything generic recursively.
What can you suggest?
@@adiviuh Try to refactor into composition instead of inheritance
@@MilanJovanovicTech I'm not sure, that it is the best option here) Thanks for answer.
Does this remove the need for an id property a base entity class then? Because each entity will have a specific id type.
I've been going over your pragmatic clean architecture course, and in there, you have a base class for entities that takes a guid id for the constructor. But if that base property gets removed, i lose the safety of making sure each entity has an id defined.
I tried making the entity bass generic with a TId, but it seems to be getting out of hand fast.
I had that in the course in V1. But I removed it, because it adds too much complexity for not much value.
@MilanJovanovicTech makes sense. Thanks for the new content, also!
Do you have in mind to make a video about integration testing using a docker container? Thanks
Yes
Awesome and Useful content
Glad you think so!
Thank You Millan .
I have a question
If I have a Person entity and a Customer entity and the Customer entity inherits the Person entity
How do I use Strongly Typed ID
In this case
For example, if you create a strongly typed id for the person entity and another for the customer entity, but since the customer entity inherits the person entity, it certainly gets the person entity id. How do we solve this problem?
Use the PersionId?
How we can use strongly typed ids with database generated values
I do believe if you configure it with EF Core it'll work just fine, but I didn't check with DB generated IDs
Would it be an idea to call new guid by default inside the default constructor of a strongly typed id so that new guid isn't called outside the new record. It Could still make muddle up if you write new LineItem(new LineItemId(product.Id.Value).....), even though that seems ridiculous 😅😅
Or a static Create method
Hey, do you have some books for reference about ddd?
- Domain-Driven Design, Eric Evans
- Learning Domain-Driven Design, Vlad Khononov
On one hand I am a big fan of strongly typed entity identifiers, but i really wish this demo used record structs instead of just records. In the domain model, identifiers are almost always conceptualized with value-type semantics and not reference-type semantics. I think turning a primitive type identifier into a domain-specific *reference* type identifier throws the baby (value-type semantics) out with the bathwater and ends up turning lists of identifiers from something fast, lightweight and space efficient in the primitive obsessed world, into something that ends up being heap allocated for every identifier. I think part of the reason for primitive obsession is the simplicity and performance. With record structs you get it all - a clean type-safe domain model AND all the safe performance and space-savings of value types.
What did you use before we had records?
@@MilanJovanovicTech I use a readonly struct type to model these domain ids and implement overrides for equals, operators, hashcode and casting operators to streamline conversion back and forth to primitive types for DTO's to the database
Hello Milan, why would you choose guid over int pk in ddd
Based on what are we making the decision? Is DB performance important?
Yo, Milan. Did you ever get an exception while creating dbContext called "Unable to create a 'DbContext' of type ' '.The entity type 'ExampleEntityId' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'"?
Even though I have specified this entity as the primary key of my class using fluent api. Can't find a solution.
Did you also configure a converter for the ID?
@@MilanJovanovicTech
Yes i did:
builder.Property(p => p.Id).HasConversion(
categoryId => categoryId.Value,
value => new CategoryId(value));
that's why i find this exception weird.
@@MilanJovanovicTech I fixed the exception. It consisted in the fact that I incorrectly configured the foreign key for an entity in another class.
How was it:
builder.HasOne(x => x.CategoryId)
.With Many()
.HasForeignKey(x => x.Id);
How to do it correctly:
builder.HasOne()
.With Many()
.HasForeignKey(x => x.CategoryId);
This doesn't always work, for instance with composite keys.
Tables Tenant(TenantId) and TableA(TenantId, AId).
Now TableB(TenantId, BId) wants to link to TableA with extra field (AId) so TableB(TenantId, BId, AId). The TenantId is now shared; if you don't share the key you risk links to data belonging to another tenant! If you use a strongly typed id then the TenantId gets duplicated, is redundant and can still point to another tenant.
That makes no sense. If you pass the same TenantId to both TableA and TableB how can it be the wrong tenant?
@@MilanJovanovicTech If you use strongly typed ids, then TableB would be (TenantId, BId, TenantId2, AId) - first two fields are the key for TableB, second two are the key reference to TableA; now there is nothing stopping you from linking an entry in TableB to an entry in TableA that belongs to a different tenant.
@@swozzares So why not have (TenantId, BId, AId) - where the TenantId is shared?
Same can be done in the code
@@MilanJovanovicTech Thats what I said in the first place! lol, I don't think you are understanding the point.
Another very useful DDD concept that I've learned from @MilanJovanovicTech is the use of value objects. But I'm a little unclear as to the difference between the two. Would a value object not have been just as suitable, or better, to use for the ID? Would anyone care to explain the difference?
Records are immutable, so you can consider this a value object. Don't think about a value object as "it inherits from a class". Think about it from the qualities it has: immutable, represented by its value.
But I would like to know, what's the real benefit about it?
I also don't get the point. Dealing with Api and Database Logic will getting be pain in the you know what
@@Kingside88 The real benefit is that you can't accidently transpose parameters when calling methods.
Database logic isn't a problem for us as we never pass the domain object directly to EF Core. We always create a data transfer object (DTO) from the domain object and use that. Similarly, when reading from the database, we reading into a DTO and then create the domain object from the DTO.
The only benefit I see is not being able to the use the identity where you’re not supposed to. Example, using a UserId to get an Office. Obviously doing this would result in a bug, but with this solution, it would cause a design-time error.
@@jpsytaccount Yes - and that's a massive benefit. The quicker you pick up these mistakes the cheaper it is to fix it. You'd like to think that unit testing and QA will pick up these mistakes, but in the real world, that doesn't always happen. Even if they do, the cheapest bug to fix is still the one that the compiler detects..
Others have chipped in with some pretty good benefits. I like it because it makes everything more expressive with strong typing.
Of course if you feel this is overkill, no need to use it. I don't like to be dogmatic about any design pattern, and leave it up to the developer to pick and choose what to use.
Maybe using readonly record struct is a better idea? A class seems like an unnecessary indirection here.
Not a bad idea
Great video as usual. But, when using Records this way, you are taking a performance and memory hit.
Based on your use, it may be better to use a struct with IEquatable as shown in the example below.
public struct CustomerId : IEquatable
{
public CustomerId(Guid value) => Value = value;
public Guid Value { get; }
public bool Equals(CustomerId other) => Value.Equals(other.Value);
public override bool Equals(object? obj) => obj is CustomerId other && Equals(other);
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(CustomerId left, CustomerId right) => left.Equals(right);
public static bool operator !=(CustomerId left, CustomerId right) => !left.Equals(right);
public override string ToString() => Value.ToString();
}
Isn't that too verbose? Especially with many strongly typed IDs. I have to explore record structs, though
@@MilanJovanovicTech, it is a little verbose. It would be a mater of deciding if being a little verbose with worth not taking the performance and memory hits for using Records. I believe Records use reflection under the hood and that could be why there is that hit.
Benchmark-ing might help make the decision between the four: class, struct, record and record struct.
This has got me intrigued.
Hey Mate, great video :) Could you make one on how to define a composite key for a join table? I.e. Key is two foreign keys to entities a & b. I've got a requirement for a join table between two entities that also has extra info associated with it. Not entirely sure how to implement that with DDD
Use the raw values, it'll make your life easier
Hey Milan! Where's your new MVP trophy? Add it to the background :) ! 'Til next time!
Hasn't arrived yet 😅
Why not struct?
No particular reason
Usage of strongly typed ids for Guid, int or long just misunderstood. If you have strongly typed ids you should define custom business identifier instead of database id. Let's say you have TrackingId for your shipment, that TrackingId should be generated in domain layer with logic.
But that would still be a tracking ID in the DB?
Good work Milan!,
my Question is out of this topic. Note:- My english isn't good enough 😅 i hope you will get the point?
we use Automapper to map mostly DTO & DB models, which saves lines of code for assigning.
Question is if we use mapper inside .Select(s => _mapper(s)) during fetching data using Efcore then it maps on client site but i want to map data on server side,
how to handle automapper to map data on server side.
and i'm still waiting for SHARED Culture video in net core. as few days earlier you said you will do it.
`SHARED Culture` remind me about that one again? 🤔
I think you should take a look at AutoMapper Projections to map on the DB side
@@MilanJovanovicTechif you can provide just a short, that will be helpful.
Thanks
@@MilanJovanovicTech 'Shared Culture' Globalization & localization
yes automapper projections.. I thinkt the automapper library has an IQueryable extension method called ProjectTo()
What is benefit
- Explicitness
- Expressiveness
- Better overall design
@@MilanJovanovicTech over engineering
Hello Milan, I was looking for your email but it seems you already answer questions in the comments and I appreciate that. Recently I have a question and the first person that came to my mind to ask it was you. In a book I was reading, it suggested to ask someone who has gone the path you want to go, the question of how they think about their experiences and whether the effort was worth it to get there. As a software engineer, how would you go about this question? I respect what you’re doing, but I imagine it has
high points and low points. Could you share them with me? Knowing what
you know now, is it worth the effort?
Anything worth achieving is always worth the effort :)
Sounds a bit cliche, but it's true. Was it hard getting to where I'm at? Heck yeah. But I don't regret it. The highs are awesome, and the lows can be painful. What I try is to keep moving "forward" each day, at least a little. Wherever "forward" is (you have to decide for yourself). Not sure if this answers your question.
@@MilanJovanovicTech If I'm not not troubling you with my questions, If you were to go back, would you go into a different field? why?
thanks for the the video
No problem!
Oh boi, this is really confusing I though in your recent video you said the entities could be within Infrastructure but now you are using in the Domain project; haaah haha. My name is also true for coding, it seems.
I doubt I said entities go in Infra - which video?
@@MilanJovanovicTech You are right, sorry I was thinking about the repository interface; it's a tiny bit complicated because there are a lot of data to take in. In mean timetime watched more of your video and there seems to be multiple ways, so it's not strange anymore. 😅
We can use struct record instead of class record. I believe.
Yes
Primitive types with Named Parameters..Wont the same problems occur if you have more than one Parameter with the same strongly type id.
Yes, but less likely scenario
its cool, but instead of passing OrderId for example we can pass the Order itself
If you prefer that approach, yeah
I think I prefer nick’s example: ruclips.net/video/z4SB5BkQX7M/видео.html
He's using the library I recommended at the end of the video. It's a bit out of date at the moment, but still an okay solution
Ah, I hopped off at like 90% so I missed it! Regardless, thanks for sharing. Always enjoy seeing how others like to work
Thanks for the link. Nick's video does give some much needed context with dto/API and domain and doesn't wrap a struct in a class. Still think this is borderline over engineering, but seeing the API to domain conversion and serialisation makes me think there is a good argument for these.
This is what is known as over-engineering and there is more code with barely any real world benefit.
In your last example with the repository get by ID method you already know that you need the ID itself because the method is called "GetByID" so the code will typically read "myThingRepo.GetByID" and it should be returning a singular of . If someone can't work that out then they shouldn't be using a text editor.
Also I've seen primitive obsession where you have long list parameters where everything is a string or int. A struct / class is the obvious replacement. What you are presenting here isn't really primitive obsession. Is really passing a Guid to a clearly labelled method really primitive obsession?
The only example I think where it might be beneficial is in a constructor example with many IDs. Even then it like marginal benefit. You could just use a class with clearly labelled IDs.
I agree it leans towards over-engineering, but that's if you aren't too keen on DDD in the first plsce
@@MilanJovanovicTech I don't think DDD requires over-engineering.
@@dave7244 There are many flavors of DDD
@@MilanJovanovicTech Doesn't really answer the criticism.
I can achieve exactly the same thing is a regular Guid Id field.
I agree, this seems to just add complexity where there is no need. The agrument about the method signature being strongly typed seems a little silly, well what if I can that code an create a new productId instead of passing the productId, this feels just as stupid as not using the correct ordering (the argument names do serve a purpose).
I feel like videos like these mislead beginners into over engineering.
I'm not convinced for the need of strongly type Ids. They look cool, but they have very little to none practical usefulness. There's not much primitive obsession in this case because there's not much to encapsulate in my opinion.
There's no need for it, sure. What I did wrong in this video is not using a better example for the ID. Instead of just wrapping a primitive type, I should've shown a "meaningful ID". I'll make a post about it. In general, strongly typed IDs _can_ bring some qualities that you may find useful, so that's when you should consider using it.