How Strongly Typed IDs Can Make Your Code More Expressive | DDD

  Рет қаралды 36,441

Milan Jovanović

Milan Jovanović

Күн бұрын

Пікірлер: 210
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
@rahulaga
@rahulaga Жыл бұрын
you truly stand out differently by giving practical examples..and during the course we got to know many other new things as side effect 😊
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Wow, thanks a lot Rahul! This made my day :)
@marna_li
@marna_li Жыл бұрын
A tip: You can implement IParsable so you can use these strongly typed Ids for arg in your controllers and endpoints.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I would honestly leave them out of the controller endpoints and just use primitive types, but that's just my opinion
@marna_li
@marna_li Жыл бұрын
@@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.
@modernkennnern
@modernkennnern Жыл бұрын
@@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
@pete9049
@pete9049 Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
Welcome aboard, Pete 😁 Glad you found the channel
@feefifofum6383
@feefifofum6383 Жыл бұрын
Love it. Could you do a follow up showing exactly how to integrate it with efcore…. That would be great.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Yes, the EF Core + DDD Mapping video is coming out next Friday :)
@krccmsitp2884
@krccmsitp2884 Жыл бұрын
@@MilanJovanovicTech Great! I'm looking forward to it.
@sunnypatel1045
@sunnypatel1045 Жыл бұрын
Love your videos keep up with them! I be keen to see your take on testing strategies within your DDD projects.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Unit testing DDD is pretty easy, don't you think?
@sunnypatel1045
@sunnypatel1045 Жыл бұрын
@@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.
@krccmsitp2884
@krccmsitp2884 Жыл бұрын
I like the approach and in our current project we actually apply it. With EF Core it is also flawless to be persisted.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Is there anything I could have added in the video?
@medzik1993
@medzik1993 Жыл бұрын
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 ?
@krccmsitp2884
@krccmsitp2884 Жыл бұрын
It depends, as often. You can, however, choose between "record [class]" and "record struct" in C#.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I think it's cherry picking, but I'm not as keen on micro-optimizations.
@siavash2176
@siavash2176 Жыл бұрын
Why not use readonly struct record? Why should I allocate one more extra object on the heap?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
If you care about that extra object, go with a struct
@arthuraugsten
@arthuraugsten Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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.
@Ahmedhosnybarbary
@Ahmedhosnybarbary Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
@spontaneousorder5670
@spontaneousorder5670 Жыл бұрын
Great presentation. I'm just starting a new project and I'm going to use strongly typed IDs, for sure.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Good luck and be careful of overengineering
@DrGaurangGupta
@DrGaurangGupta Жыл бұрын
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.
@stjepanmajdak7396
@stjepanmajdak7396 Жыл бұрын
What about record struct?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Viable option - what about passing it around?
@RicardoValero95
@RicardoValero95 Жыл бұрын
Why would you place the id record on a different file? Why not before the class where you’re using it?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I (almost) never place two classes in the same file
@dcernach
@dcernach Жыл бұрын
That is great, but how can use these strongly types ids with Entity Framework?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Showing that in Friday's video
@dcernach
@dcernach Жыл бұрын
@@MilanJovanovicTech
@nayanc4353
@nayanc4353 Жыл бұрын
A record doesn't enforce immutability. It is syntactical sugar for class + ctor + init-property. You can have mutable properties in record.
@eugeneshevchenko4911
@eugeneshevchenko4911 Жыл бұрын
It'd better to use record struct for immutability.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
If you try hard enough nothing is immutable :) A record define like in the video is immutable.
@fxandrei
@fxandrei Жыл бұрын
Great stuff. Have you even thought about doing mini tutorials like this for frameworks like ABP or something similar ?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I don't think covering frameworks makes sense if I didn't use them in Production
@fxandrei
@fxandrei Жыл бұрын
@@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.
@KashmirThakur-goldi
@KashmirThakur-goldi Жыл бұрын
How we can use strongly typed ids with database generated values
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I do believe if you configure it with EF Core it'll work just fine, but I didn't check with DB generated IDs
@syedib
@syedib Жыл бұрын
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 { }
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I'll try it out and see, but something like AggregateRoot should be possible
@whoost
@whoost Жыл бұрын
I have the same question....coulnd't get it to work with AggregateRoot
@sandrorevazishvili1113
@sandrorevazishvili1113 Жыл бұрын
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 ?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Can't say much without seeing your code
@andreasmewald2439
@andreasmewald2439 Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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
@PrisonerZ3RO
@PrisonerZ3RO Жыл бұрын
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)
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
This is a valuable perspective to have
@mcorven04
@mcorven04 Жыл бұрын
Thanks as always, Milan! How would you go on to implement the EF conversions for read and write?
@steve-wright-uk
@steve-wright-uk Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Coming out in Tuesday's video
@ryoman76
@ryoman76 Жыл бұрын
Great! one question can I type an integer (in the db it will become incremental) instead of a GUID?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I think it should work, but you should test that first
@AshrafSada
@AshrafSada 9 ай бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 9 ай бұрын
Yes
@JohnOliverAtHome
@JohnOliverAtHome Жыл бұрын
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());
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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.
@andreasmewald2439
@andreasmewald2439 Жыл бұрын
What about casting operator ( Guid => ProductId) in the record itself?
@JohnOliverAtHome
@JohnOliverAtHome Жыл бұрын
@@andreasmewald2439 Using an implicit operator could work. public static implicit operator Guid(CustomerId value) => value.Value;
@andreasmewald2439
@andreasmewald2439 Жыл бұрын
@@JohnOliverAtHome it‘s less noisy and if you‘re using rider, rider will show you the implicit convertion in the inlay hints
@stefan-d.grigorescu
@stefan-d.grigorescu Жыл бұрын
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
@vagnerwentz1189
@vagnerwentz1189 Жыл бұрын
But I would like to know, what's the real benefit about it?
@Kingside88
@Kingside88 Жыл бұрын
I also don't get the point. Dealing with Api and Database Logic will getting be pain in the you know what
@steve-wright-uk
@steve-wright-uk Жыл бұрын
@@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.
@jpsytaccount
@jpsytaccount Жыл бұрын
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.
@steve-wright-uk
@steve-wright-uk Жыл бұрын
@@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..
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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.
@ezecel9
@ezecel9 Жыл бұрын
Do you have in mind to make a video about integration testing using a docker container? Thanks
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Yes
@ДенисЕгоров-ь3в
@ДенисЕгоров-ь3в Жыл бұрын
There is an issue using strongly typed ids. Npgsql provider for EF Core doesn't support them as well as SQL provider.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Oh really? Well the next video is how to configure it with EF Core
@ДенисЕгоров-ь3в
@ДенисЕгоров-ь3в Жыл бұрын
@@MilanJovanovicTech try to configure it using HiLo. Also you can check issue #2617 in Npgsql provider for PostgreSQL repository
@piotr6254
@piotr6254 Жыл бұрын
@@MilanJovanovicTech I was goin to ask if you will do video about configuring it, but found the answer here :D
@michaldivismusic
@michaldivismusic Жыл бұрын
What about creating a CustomerId struct that simply inherits from Guid. Or is that impossible?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Isn't inheritance not supported with structs?
@michaldivismusic
@michaldivismusic Жыл бұрын
@@MilanJovanovicTech Oh, ok. The thought just came to me whole watching the video, nevermind then.
@z_prospective160
@z_prospective160 Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Yes - but what about the code creating that strongly typed ID? It'll also need to be updated
@unimatrix20a
@unimatrix20a 11 ай бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
Yes, but it becomes messy pretty fast
@foonlam7134
@foonlam7134 Жыл бұрын
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?
@JohnOliverAtHome
@JohnOliverAtHome Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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.
@kiokokitheka
@kiokokitheka Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I'm not sure about the mapping. A value converter maybe. But I suggest you test the query support.
@dotnetMasterCSharp
@dotnetMasterCSharp Ай бұрын
Awesome and Useful content
@MilanJovanovicTech
@MilanJovanovicTech Ай бұрын
Glad you think so!
@Andy01010
@Andy01010 Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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.
@Andy01010
@Andy01010 Жыл бұрын
@@MilanJovanovicTech these days developing software requires a rather high degree of being open minded
@greatpowerplay
@greatpowerplay Жыл бұрын
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?
@pedrofpsimoes
@pedrofpsimoes Жыл бұрын
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"
@bartomiejoryszak6645
@bartomiejoryszak6645 Жыл бұрын
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
@dave7244
@dave7244 Жыл бұрын
It concerns me anyone would call this a "wonderful idea".
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Just serialize the raw value at the DB value. Not the object itself.
@krccmsitp2884
@krccmsitp2884 Жыл бұрын
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.
@z_prospective160
@z_prospective160 Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
As you suspect, it introduces a lot of boilerplate if you want to generalize around it. Win some, lose some - right?
@jakecosilla
@jakecosilla Жыл бұрын
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; } }
@DarlissonLimeira
@DarlissonLimeira Жыл бұрын
Hey, do you have some books for reference about ddd?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
- Domain-Driven Design, Eric Evans - Learning Domain-Driven Design, Vlad Khononov
@europeanleaguestaff
@europeanleaguestaff Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
It's too problematic, probably want to avoid it with composite keys
@harryh212
@harryh212 Жыл бұрын
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 😅😅
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Or a static Create method
@winstochurgle9133
@winstochurgle9133 9 ай бұрын
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
@MilanJovanovicTech 9 ай бұрын
Did you also configure a converter for the ID?
@winstochurgle9133
@winstochurgle9133 9 ай бұрын
@@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.
@winstochurgle9133
@winstochurgle9133 9 ай бұрын
@@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);
@dionismendanha4649
@dionismendanha4649 Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
Yeah, but you'll have to make Entity class generic
@adiviuh
@adiviuh Жыл бұрын
@@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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
@@adiviuh Try to refactor into composition instead of inheritance
@adiviuh
@adiviuh Жыл бұрын
@@MilanJovanovicTech I'm not sure, that it is the best option here) Thanks for answer.
@adamlazar2355
@adamlazar2355 11 ай бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 11 ай бұрын
I had that in the course in V1. But I removed it, because it adds too much complexity for not much value.
@adamlazar2355
@adamlazar2355 11 ай бұрын
@MilanJovanovicTech makes sense. Thanks for the new content, also!
@swozzares
@swozzares Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
That makes no sense. If you pass the same TenantId to both TableA and TableB how can it be the wrong tenant?
@swozzares
@swozzares Жыл бұрын
@@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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
@@swozzares So why not have (TenantId, BId, AId) - where the TenantId is shared? Same can be done in the code
@swozzares
@swozzares Жыл бұрын
@@MilanJovanovicTech Thats what I said in the first place! lol, I don't think you are understanding the point.
@blackmagic9921
@blackmagic9921 7 ай бұрын
Hello Milan, why would you choose guid over int pk in ddd
@MilanJovanovicTech
@MilanJovanovicTech 7 ай бұрын
Based on what are we making the decision? Is DB performance important?
@luboshemala3485
@luboshemala3485 Жыл бұрын
Why not struct?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
No particular reason
@MahmoudSaed98
@MahmoudSaed98 5 ай бұрын
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
@MahmoudSaed98
@MahmoudSaed98 5 ай бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 5 ай бұрын
Use the PersionId?
@matthewrossee
@matthewrossee Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
Why would an Order have a List however? Wouldn't it have Order -> List and then LineItem -> ProductId?
@matthewrossee
@matthewrossee Жыл бұрын
@@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 (kzbin.info/www/bejne/eGSsomZqa7Cqi7M) 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.
@crikey4336
@crikey4336 Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
What did you use before we had records?
@crikey4336
@crikey4336 Жыл бұрын
@@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
@tryagain6148
@tryagain6148 Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
This won't add 5 SP don't worry
@tryagain6148
@tryagain6148 Жыл бұрын
​@@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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
@@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
@justtomi-qp8qj
@justtomi-qp8qj Жыл бұрын
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!
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I appreciate all perspectives. I think it's a matter of finding a bug at design time vs runtime. Which do you prefer?
@justtomi-qp8qj
@justtomi-qp8qj Жыл бұрын
@@MilanJovanovicTech well in that question you left me no choice 😅
@vincentcifello4435
@vincentcifello4435 Жыл бұрын
@@MilanJovanovicTech Are you saying that your Unit Testing of the Domain would NOT find a bug caused by mistaken argument transposition?
@batuhanaydn4592
@batuhanaydn4592 Жыл бұрын
Maybe using readonly record struct is a better idea? A class seems like an unnecessary indirection here.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Not a bad idea
@vamvdotnet
@vamvdotnet Жыл бұрын
Hey Milan! Where's your new MVP trophy? Add it to the background :) ! 'Til next time!
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Hasn't arrived yet 😅
@Mr43046721
@Mr43046721 Жыл бұрын
Its great idea for a global reactor of a big project)) have been wanting to implement this for a long time)
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Maybe too much for a global refactor 😅
@Luke_Ainsworth
@Luke_Ainsworth 4 ай бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 4 ай бұрын
Both can work, as the responsibility is the same. I'd probably do it in the handlers though
@murat.yuceer
@murat.yuceer Жыл бұрын
What is benefit
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
- Explicitness - Expressiveness - Better overall design
@murat.yuceer
@murat.yuceer Жыл бұрын
@@MilanJovanovicTech over engineering
@Luke_Ainsworth
@Luke_Ainsworth Жыл бұрын
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
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Use the raw values, it'll make your life easier
@deefstes
@deefstes Жыл бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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.
@alirezayari-b9b
@alirezayari-b9b Жыл бұрын
thanks for the the video
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
No problem!
@EldonElledge
@EldonElledge Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
Isn't that too verbose? Especially with many strongly typed IDs. I have to explore record structs, though
@EldonElledge
@EldonElledge Жыл бұрын
@@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.
@DrGaurangGupta
@DrGaurangGupta Жыл бұрын
Benchmark-ing might help make the decision between the four: class, struct, record and record struct. This has got me intrigued.
@zameer.vighio
@zameer.vighio Жыл бұрын
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.
@zameer.vighio
@zameer.vighio Жыл бұрын
and i'm still waiting for SHARED Culture video in net core. as few days earlier you said you will do it.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
`SHARED Culture` remind me about that one again? 🤔 I think you should take a look at AutoMapper Projections to map on the DB side
@zameer.vighio
@zameer.vighio Жыл бұрын
​@@MilanJovanovicTechif you can provide just a short, that will be helpful. Thanks
@zameer.vighio
@zameer.vighio Жыл бұрын
@@MilanJovanovicTech 'Shared Culture' Globalization & localization
@z_prospective160
@z_prospective160 Жыл бұрын
yes automapper projections.. I thinkt the automapper library has an IQueryable extension method called ProjectTo()
@amirmirzababapour6016
@amirmirzababapour6016 5 ай бұрын
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?
@MilanJovanovicTech
@MilanJovanovicTech 5 ай бұрын
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.
@amirmirzababapour6016
@amirmirzababapour6016 5 ай бұрын
@@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?
@maxiklife
@maxiklife Ай бұрын
We can use struct record instead of class record. I believe.
@MilanJovanovicTech
@MilanJovanovicTech Ай бұрын
Yes
Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
But that would still be a tracking ID in the DB?
@pedroferreira9234
@pedroferreira9234 Жыл бұрын
its cool, but instead of passing OrderId for example we can pass the Order itself
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
If you prefer that approach, yeah
@tjmukurumbira
@tjmukurumbira Жыл бұрын
Primitive types with Named Parameters..Wont the same problems occur if you have more than one Parameter with the same strongly type id.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Yes, but less likely scenario
@disorientatedgamer3141
@disorientatedgamer3141 Жыл бұрын
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
@MilanJovanovicTech Жыл бұрын
I doubt I said entities go in Infra - which video?
@disorientatedgamer3141
@disorientatedgamer3141 Жыл бұрын
​@@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. 😅
@pdevito
@pdevito Жыл бұрын
I think I prefer nick’s example: kzbin.info/www/bejne/sGW2c2h4oLaLba8
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
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
@pdevito
@pdevito Жыл бұрын
Ah, I hopped off at like 90% so I missed it! Regardless, thanks for sharing. Always enjoy seeing how others like to work
@TheZeppelinShark
@TheZeppelinShark Жыл бұрын
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.
@dave7244
@dave7244 Жыл бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
I agree it leans towards over-engineering, but that's if you aren't too keen on DDD in the first plsce
@dave7244
@dave7244 Жыл бұрын
@@MilanJovanovicTech I don't think DDD requires over-engineering.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
@@dave7244 There are many flavors of DDD
@dave7244
@dave7244 Жыл бұрын
@@MilanJovanovicTech Doesn't really answer the criticism. I can achieve exactly the same thing is a regular Guid Id field.
@TheZeppelinShark
@TheZeppelinShark Жыл бұрын
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.
@danflemming3553
@danflemming3553 5 ай бұрын
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.
@MilanJovanovicTech
@MilanJovanovicTech 5 ай бұрын
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.
Is an Anemic Domain Model an Anti-Pattern?
9:54
Milan Jovanović
Рет қаралды 23 М.
Mapping Domain-Driven Design Concepts To The Database With EF Core
18:06
Milan Jovanović
Рет қаралды 56 М.
Jaidarman TOP / Жоғары лига-2023 / Жекпе-жек 1-ТУР / 1-топ
1:30:54
Маусымашар-2023 / Гала-концерт / АТУ қоштасу
1:27:35
Jaidarman OFFICIAL / JCI
Рет қаралды 390 М.
Fix Your Controllers By Refactoring To Minimal APIs
14:56
Milan Jovanović
Рет қаралды 47 М.
Building a Webhooks System in .NET From Scratch (part 1)
13:44
Milan Jovanović
Рет қаралды 13 М.
Java Method References - A Beginner's Guide
12:59
Dan Vega
Рет қаралды 9 М.
This is the Only Right Way to Write React clean-code - SOLID
18:23
Protocols vs ABCs in Python - When to Use Which One?
15:31
ArjanCodes
Рет қаралды 45 М.
How I Use The Generic Repository Pattern In Clean Architecture
17:15
Milan Jovanović
Рет қаралды 41 М.
Forget Controllers and Minimal APIs in .NET!
14:07
Nick Chapsas
Рет қаралды 81 М.
Mastering DDD Aggregate Modeling With THESE 3 Steps
17:26
Codewrinkles
Рет қаралды 12 М.
How to Use Value Objects to Solve Primitive Obsession
13:54
Milan Jovanović
Рет қаралды 48 М.
пранк: псих сбежал из дурдома
0:53
Анна Зинкина
Рет қаралды 1,7 МЛН
ПОСТАРЕЛА ЗА 1 ДЕНЬ НА 20 ЛЕТ - МУЖСКОЕ ЖЕНСКОЕ
55:44
ПРИЯТНЫЙ ИЛЬДАР
Рет қаралды 677 М.
Американцы красят асфальт?
0:27
BAZAR CLUB
Рет қаралды 188 М.
BIP HOUSE  .бип хаус 🥰🏡  #shorts
0:13
bip_house
Рет қаралды 1,2 МЛН
Когда перепутал график девушек😁🐣
0:24
Alexey Merinov
Рет қаралды 3,1 МЛН