The Identity Paradox | DDD, EF Core & Strongly Typed IDs

Sdílet
Vložit
  • čas přidán 22. 05. 2024
  • Why does everyone recommend using strongly typed IDs if this doesn't work with EF Core?
    This video will break it down and show a creative hack that elegantly solved this issue.
    Get the source code: / amantinband .
    Link to the entire playlist: • ASP.NET 6 REST API Fol...
    Connect with me on 'em socials:
    Twitter: / amantinband
    LinkedIn: / amantinband
    GitHub: github.com/amantinband
    00:00. The Problem
    07:40. Paradoxical Solutions 👎
    10:53. Better Solution 👍
  • Věda a technologie

Komentáře • 103

  • @ahmedrizk106
    @ahmedrizk106 Před rokem +25

    I'm actually one of the people who opened a github issue for this, and let me just say after weeks of exhaustive solution implementations I'm really glad you are back. 👏❤

  • @misonosenpai3168
    @misonosenpai3168 Před 5 měsíci +8

    Now this problem is fixed on EF core 8, but this is a very helpful tutorial, i can't find any course that teach about DDD better than your course

    • @troelsmortensen9914
      @troelsmortensen9914 Před 4 měsíci +2

      Do you by chance have a link to where I can see this solution? I can't find the fix.

    • @OldShoolGames
      @OldShoolGames Před 3 měsíci +2

      Do you have any link to it ?

    • @yougayan
      @yougayan Před 6 dny

      Yep, this is definitely fixed in EF Core 8, just tried it.

  • @MilanJovanovicTech
    @MilanJovanovicTech Před rokem +23

    Good to see you back 🔥

    • @nawarali1912
      @nawarali1912 Před rokem +5

      why you don't do something together 😄 you both provide the domain driven design and design patterns in the best way

    • @alan-
      @alan- Před rokem +5

      @@nawarali1912 I agree. Milan + Amichai = CA + DDD

  • @jorgeurielcarballohernande9886

    Wow! I started this amazing serie about CA and DDD and I can't stop. Congrats for your job. No words to describe the effort and the passion put in this. Again thousand thanks @Amichai 👌🤓.

  • @carloswinstonjavierllenas3117

    Many thanks. I found myself Laughing Out Loud when you reached the not recommended solutions because I tried the first three in my pet project where I'm trying all you taught us in these videos, and discarded the first two because of the SAME reasons.

  • @davemasters
    @davemasters Před rokem

    Back with a bang!
    I struggled with this and ended up succumbing to your 2nd bad solution. Look forward to going back to implement this solution!

  • @qaphuikpoh
    @qaphuikpoh Před rokem +3

    Good to see you back 🎉

  • @poddev
    @poddev Před rokem +7

    Yes I agree is nice to have our ids properties strongly tipped in the other hand sometimes I feel this is too over engineering which is the opposite of clean code.

    • @amantinband
      @amantinband  Před rokem

      I tend to agree. I think there very specific applications need to follow these practices to the T

  • @shoooozzzz
    @shoooozzzz Před rokem +4

    Ahhhhh yeah, he's back! So happy we get more top tier content.

  • @Codewrinkles
    @Codewrinkles Před rokem

    Nice one. Welcome back!

  • @alexandercarlsen2038
    @alexandercarlsen2038 Před rokem +10

    I usually keep the inner identifier private and implement implicit and explicit conversions to string or whichever inner type we are using. This way, all of the domain code doesn't know, but any dependencies (like EF or something like a httpclient can use the string as needed)

    • @amantinband
      @amantinband  Před rokem

      I like that approach as well

    • @matthewrossee
      @matthewrossee Před 8 měsíci

      Do you have any repo on GitHub where I could look at your solution? It seems interesting.

  • @baulron
    @baulron Před 2 měsíci

    Amazing content. Thank you!

  • @LoZioIAR
    @LoZioIAR Před rokem

    Great!! You are back!!

  • @tplummer217
    @tplummer217 Před rokem

    Great stuff, thanks!

  • @vagnerpadilha3485
    @vagnerpadilha3485 Před rokem +3

    Amazing to see you again. A question. why do you prefer StronglyId has a "class" type Wouldn't "record struct" or "struct" be preferable? To lower the GC pressure?

    • @amantinband
      @amantinband  Před rokem +2

      Generally yes. The architecture I’m demonstrating in this series isn’t very GC friendly, especially with all the various objects and MediatR, so memory/runtime sensitive applications should probably model their system differently.
      But to your question, I haven’t given struct enough thought here to say if non-destructive mutation or stack allocations can present issues. I’ll have to play with it and come back to you 🙂

  • @jamesevans6438
    @jamesevans6438 Před rokem +5

    Welcome back - I've never modeled a domain to this extent using EF Core so not hit the problem you were facing but I like the solution, seems nice and clean, no real downside?

    • @amantinband
      @amantinband  Před rokem

      It has some overhead, complexity and requires breaking “persistence ignorance”, but out of the solutions, this is definitely better IMO

  • @timschmidt5469
    @timschmidt5469 Před rokem

    Welcome back and awesome video! I love this series! I think the simplicity of the "creative hack" is worth the minor costs of developer dogmatism :) Your solution requires much less work and maintenance than "the right way" AND you're having to do this because of the limitations of known issues that pretty much have "Won't Fix" resolutions (at least not anytime soon). Fantastic job!

  • @martinmagpantay4226
    @martinmagpantay4226 Před 6 měsíci

    Hi Amichai! I have a question,
    For queries with multiple related entities like getting the Menu with the list of Dinners and MenuReview which sits on different Aggregate Root. What do you think you'll use on that?
    I'm so happy to subscribe to your Patreon. You're such a blessing in the community. :)

    • @martinmagpantay4226
      @martinmagpantay4226 Před 6 měsíci

      Also, for querying only details of an entity which is not an aggregate root. Thank you!

  • @prashlovessamosa
    @prashlovessamosa Před rokem +7

    Long time pal.

  • @sphrtehrani
    @sphrtehrani Před 9 měsíci +3

    Hi, great series. In DDD we use GUID for Id type because entities ids must be unique across our domain. But in database using GUID as primary key (with clustered index) has performance issue specially in heavy insert scenarios because of randomness of GUID Ids.
    What can we do about it?

    • @nitrovent
      @nitrovent Před 8 měsíci +1

      There are solutions for index-friendly GUIDs such auto-incrementing GUIDs. The ABP framework e.g. implements a GUID generator that generates pseudo GUIDs for that purpose.

    • @md.redwanhossain6288
      @md.redwanhossain6288 Před měsícem

      Use ULID based GUID

  • @MaximShiryaevT
    @MaximShiryaevT Před rokem +4

    IMHO, the main problem is an existence of Id properties in the domain objects. If we've got rid of foreign keys in dependent objects using shadow properties, for me it's just absolutely necessary to make Id properties shadow as well. No Id - no problem. Ids are DB realm concept, not object one. What do you think?

    • @amantinband
      @amantinband  Před rokem +1

      Have you tried this in a project before? Do you have an example you can send me?

  • @maxbitran
    @maxbitran Před 6 měsíci

    My strongly typed Id are all structs and I have generic converters for json serialization and EF Core. In the database they all became strings, like the Guids, DateTimes (in most dbs), etc. There should be no reason for you to want to keep more than that in the database, if it is simply an Id and a value object. The problem you mentioned is related to complex objects, EF Core supports working with them and, if I am not mistaken, in future versions, they will support save them as json in the database, like document dbs. If you can give me more information, I can try to understand why this is such a big deal for you to make a whole video mentioning it as an "unsolvable" paradox. All the best.

  • @radiosh66
    @radiosh66 Před rokem

    Hi! Thanks for the video. I think it's too many generics in this solution- too complex. Btw, what tool do you use to draw on the screen?

    • @amantinband
      @amantinband  Před rokem

      Generics usually make the code more complex and harder to understand. I don't think most applications need this kind of overhead. I use Presentify for the arrows and rectangles 🙂

    • @radiosh66
      @radiosh66 Před rokem

      @@amantinband Yes, thank you for the answer and for the great content!

  • @jose49716
    @jose49716 Před rokem +2

    Which technology are you using for that slides and arrows? It's pretty awesome.

    • @amantinband
      @amantinband  Před rokem

      Thanks! Slides - Figma. Arrows - Presentify.

    • @jose49716
      @jose49716 Před rokem

      Thanks for letting me know!!! Great content.

  • @troelsmortensen9914
    @troelsmortensen9914 Před 3 měsíci

    Was the correct part of the migration shown at the end? The example is about Host having many MenuIds. But we see a table of MenuDinnerIds, i.e. combining Menu and Dinner?
    And it looks like you would only get one foreign key constraint, back to the "owner", i.e. to Host (or, in the shown migration to, Menu). But there is no FK constraint on the MenuId, so you can have invalid references in the database?
    Generally I would expect the end result to be a join table in the database, with references to both Host and Menu. Your result ends up with only one foreign key.

  • @ayalamac
    @ayalamac Před rokem

    Welcome back! Where were you? Missing your videos. Now, I see an upgrade in your drawings. What is the tool now? It seems it is not longer ZoomIt! Good job again!

    • @amantinband
      @amantinband  Před rokem

      Presentify. ZoomIt doesn't work on MacOS 😢

  • @atlesmelvr1997
    @atlesmelvr1997 Před rokem +5

    It's not a common bug to put the wrong id in the wrong column, and you can even enforce to write them as (userId: userId, tenantId: tenantId) to read it better. And the penalties you get is a lot worse than what you get (that's not needed). You have less readability, more code to write everywhere, it's slower and use more cpu. All this for a non problem.

    • @amantinband
      @amantinband  Před rokem +3

      I share your opinion most of the time. I’m covering the religious DDD approach for educational purposes.
      However, there are cases where strongly typed IDs can be helpful, and I don’t think we should disregard them as a whole. I deal with versioned string-typed IDs that are a combination of 4 or 5 other IDs regularly within Microsoft. This is a prime example of where strongly typed IDs, regardless of DDD would make several code bases less error-prone

  • @evgeniysir4220
    @evgeniysir4220 Před 10 měsíci

    I have 2 unrelated sets of tables (Menus and Hosts) after adding the Host aggregate code and adding its tables to the database. Is this how it should be? Isn't the HostMenuIds table a many to many relationship table for two aggregates Menus and Hosts? Should these two aggregates be linked, or was it originally intended to make an unlinked set of aggregate tables for further division into microservices with separate bases?

    • @matthewrossee
      @matthewrossee Před 8 měsíci

      In the BuberDinner's codebase as far as I know you're supposed to raise a new domain event like "record MenuCreatedDomainEvent(MenuIn menuId, HostId hostId) : IDomainEvent". When saving changes to the database, the saving changes interceptors should publish this event and the MenuCreatedDomainEventHandler : INotificationHandler should take care of linking the MenuId to the Host. So it could look like this: var host = _hostRepository.GetById(notification.hostId), then check if host is not null, and eventually perform the linking operation: host.AssignMenuId(menuId). Remember not to call SaveChangesAsync, you don't need any unit of work in the event handler, because when all event handlers have finished their work the dbcontext is gonna save changes. I guess for a distributed system you could imagine a situation when domain event handlers publish some sort of integration event to a message queue like RabbitMq, so the other microservices can update their db state. I hope it helps!

  • @VahidRassouli
    @VahidRassouli Před 7 měsíci

    Hi and thank your for your great afford.
    I have a question! What about the MenuId column, in the HostMenuIds table? Shouldn't it be a foreign key to the Id column of the Menues table?
    Currently following you toturial, I'm missing this relation!
    And I think it's important to have it, in case of deleting menu, we can cascade it to delete the corresponding record in the HostMenuIds table
    Thank you very very much👌

    • @VahidRassouli
      @VahidRassouli Před 7 měsíci

      Well, I think I found the answer in the next video, Domain Events!
      As you mentioned there, aggregates are transaction boundary, And we dont want changing an aggregate to have side effect on others, and that's why we don't setup foreign keys!
      But this brings me to next question, so why do we try to set foriegn key for the host-menu relation as described in this video?! Deleting a host, would delete the menu... isn't a side effect?!

    • @md.redwanhossain6288
      @md.redwanhossain6288 Před měsícem

      @@VahidRassouli This is very impractical. DDD doesn't need to be followed to the 100%. If you don't add FK, you are risking data integrity and there is no point of using relational database then. If you do not use cascade, there is no possibility of side effect.

  • @user-nw8oi9vn9y
    @user-nw8oi9vn9y Před 3 měsíci

    Yeah, you can change a GUID to a string, but if you change a string id to a Guid, then you might break existing string values that don't meet the Guid requirements (unless I'm misunderstanding what you're recommending).

  • @mightybobka
    @mightybobka Před 8 měsíci +2

    Can it be solved by Complex Types as value object in new EF8.0?

    •  Před 6 měsíci +1

      Not yet cover. Collections of complex types are not yet supportted. Vote for the issue 31237

  • @md.arafatrahmanrana242

    Strongly typed Id is really great, but the problem you faced I found out is in the EFCore migration generation process. If you mention configuration instances that are not having the "OwnedMany()" methods first and then mention the configuration instances that have "OwnedMany()" methods, then "EFCore" is able to generate the perfect migration files. Maybe this behavior is happening because of using reflection heavily. However, I'm not sure about this. Maybe you and other experts could find out that and can issue a bug to the Github repo. 😊

  • @ahmedrizk106
    @ahmedrizk106 Před rokem

    A question here, how would you implement a many to many relationship with this approach ? for example if you have an A-aggregate who owns list and this B-Aggregate is suppose to own List this would throw the same exception as before, how can we solve this?

    • @amantinband
      @amantinband  Před rokem

      This should work. Are you referencing a list of IDs in both?

    • @ahmedrizk106
      @ahmedrizk106 Před rokem +1

      @@amantinband Yes I'm referencing a List of Ids in both and efcore throws the same type of exception when trying to add a migration.
      Aggregate-A has List
      Aggregate-B has List

    • @amantinband
      @amantinband  Před rokem +1

      @@ahmedrizk106 Perhaps try using the .NET 8 preview SDK. They fixed this error message (among others), it may give you insight to what the error is

  • @925082
    @925082 Před rokem

    Mapster giving issue with record no default contractor for type RegisterCommand, Please use ConttructUsing or MapWith

  • @jamesroot9777
    @jamesroot9777 Před 11 měsíci

    I am having issues migrating like this. It keeps telling me that (for example) no suitable c-tor was found for 'HostId', so I go ahead a make a paramterless one there (which is something you didn't have to do), then the error changes to the entity type 'HostId' requires a primary key.. etc.
    I essentially keep going in a loop, adding `HasNoKey()` to X Id Value Object, then it says that I cannot be Keyless, and suggests making the AggregateRootId keyless, which initself brings another error, and I just cannot get it to work for whatever reason, and my project is basically 1:1 with yours.
    Been debugging for a few hours now, reading stuff online, but nothing seems to be working. Would be great if anybody has suggestions.

    • @TheFeljoy
      @TheFeljoy Před 10 měsíci

      Watch closely in the video. He’s not going from the last version to this version but rather deleted the old one and is executing “add initialCreate” again

  • @blastermeteor9847
    @blastermeteor9847 Před 3 měsíci

    I thought the object-type configured with OwnsMany should be recognized as a Non-Entity Type? Can someone explain cuz he said in the video that MenuId in HostAggregate config is an Entity Type

  • @shakeuk
    @shakeuk Před rokem

    Couldn't you make use of user-defined explicit or implicit conversation on the strongly typed IDs? To make EF core see/use the encapsulated primitive?

    • @amantinband
      @amantinband  Před rokem

      That won’t work either. OwnsMany/OwnsOne defines the type as an entity type

    • @mohamedal-qadeery6530
      @mohamedal-qadeery6530 Před 11 měsíci

      @@amantinband what do you mean by OwnsMany/OwnsOne defines the type as an entity type.. what do u mean by entity type ? this video made me so confused :(

  • @craigmunday3707
    @craigmunday3707 Před měsícem

    Why are these ids called ValueObjects and not DomainPrimitives? Seems like a more descriptive name for them.

  • @ilyahryapko
    @ilyahryapko Před rokem

    2:00 Static method signature should probably return ReservationId?

  • @Eirenarch
    @Eirenarch Před rokem +2

    Currently implementing this is heavy (a lot of code and relatively complex code) that it does not justify the benefits (you also need to do work on the MVC side to make it map the ids). It would be cool if C# had some kind of type aliases where you can just give names to existing types which would make it simpler to use and support in libraries like EF and MVC as they would just need to recognize the actual type and treat the value as such while the compiler takes care of wrong usage

  • @softwaretitbits5849
    @softwaretitbits5849 Před rokem +1

    Entity framework migrations are only good for simple project. Pretty useless in a big company which needs the DB schema to be the same in all environments. Will MenuId:AggregateRoot not work if there in a implicit cast to string? For JSON serializer we need to give a conversion function. It was in my todo list that your video now reminded me to do.

  • @jwbonnett
    @jwbonnett Před 10 měsíci

    The part that I don't understand is that you said the ID is a VO, yet a VO should not have any identity, this is the key difference between an entity and VO.

  • @brendanalexander6053
    @brendanalexander6053 Před 11 měsíci +1

    I have strongly typed my cats

  • @DJHightower77
    @DJHightower77 Před 11 měsíci

    The sound was very quiet in this video.

  • @S3Kglitches
    @S3Kglitches Před rokem +6

    Your domain-layer objects should not be your EF models. Breaking single responsibility principle.
    8:00, 8:45 Mapping is overhead but that's the trade-off for robustness and having an anti-corruption layer.
    9:15 Creating a mapping cannot introduce bugs compared to switching IDs in the function arguments which definitely will and these will be very hidden bugs.

    • @markcampbell2491
      @markcampbell2491 Před rokem +1

      THANK YOU. Agree with you 100%

    • @amantinband
      @amantinband  Před rokem +1

      The objects created by EF Core’s fluent API definition *is* the anti corruption layer and the mapping

    • @GreenDimka1
      @GreenDimka1 Před 11 měsíci

      ​​@@amantinbandit is a very very very wrong use for an O/RM.

  • @ahmad_9877
    @ahmad_9877 Před 8 měsíci +1

    One of the most genius rules about programming, that I really believe it: YOU ARE'NT GONNA NEED IT.
    So, apply abstractions when they really make a difference, not in the hope of some day that they will become useful.
    Also, the reasons that you mentioned to support this pattern are true about ALL fields, not just ids, so with this approach, maybe we will have one value object per field.
    I refer to Eric Evan's opinion in his book, when he talks about standalone objects, that making dependencies adds complexity to the code and demands more effort to understand it, so when you can express a field by a primitive type, you have the chance to eliminate one dependency, and you should definitely do it, especially when you have no logic to be encapsulated with that value.

    • @md.redwanhossain6288
      @md.redwanhossain6288 Před měsícem

      Id and other fields are not the same. If id is wrong, the whole data will be in an invalid state.

  • @svorskemattias
    @svorskemattias Před rokem

    I've practiced domain driven with ef core for two years now without ever having to map up identities as entities. I don't understand why you would wanna do this? To me, it feels like this ain't a problem with the feature set of ef core, but some other misunderstanding on how you should model aggregates.

    • @amantinband
      @amantinband  Před rokem +1

      The IDs are value objects, not entities. If you’re interested in learning more, you can check this out: www.informit.com/articles/article.aspx?p=2020371&seqNum=4

    • @svorskemattias
      @svorskemattias Před rokem

      @@amantinband I know that. Thats why i wouldnt configure them as entities, as you try to do.

    • @amantinband
      @amantinband  Před rokem +1

      Oh, perhaps I wasn't clear in the video - I am talking about EF Core entity types/non-entity types, not DDD entities.

  • @CodeBallast
    @CodeBallast Před rokem

    Why make the Id property of the AggregateRootId abstract? Why not just implement it in the base class itself then you wouldn't need to override it in every inherited class.

    • @amantinband
      @amantinband  Před rokem +2

      You're right, what you're suggesting is better 🙂

    • @CodeBallast
      @CodeBallast Před rokem

      @@amantinband Keep up the good work, champ!

  • @DummyFace123
    @DummyFace123 Před rokem +1

    The best thing you can do concerning EF, is just use something that benefits your life and organization.
    EF doesn't provide any additional value, only introduces new problems.
    From knowing the intricacies about how the change tracker actually works, to knowing what linq is valid, what the valid linq translates to, to how easily it is for devteams to mangle the snapshot, to its obscured concurrency capabilities (that only the most knowledgeable devs know how to do), it is just never worth it.
    See how I didn't even mention sql performance? I don't think sql performance along is enough to choose an ORM. It really boils down to the enormous amount of learning that needs to occur in order to maintain EF applications.
    I've been working with it for over half of my career, and the development shops that wisely choose not to use EF are rewarded handsomely.
    Just use something like dapper and manage migrations through fluentmigrator or something similar.
    The non-EF shops ALWAYS have a much more pleasant time with data access and database maintenance. EF adds nothing but complexity and headaches

  • @ricardomartinez1
    @ricardomartinez1 Před 11 měsíci

    “reasons”

  • @M1stFink
    @M1stFink Před měsícem

    parameter objects for the function and unit testing. problem solved.
    Why coming up with some new creative ideas to clutter a project with yet another approach for already solved problems?

  • @GreenDimka1
    @GreenDimka1 Před 11 měsíci

    Your problem lays in the architecture. Do not use EF in your business logic and you will not face such kind of problems.
    (if problem does not exist, a solution for it is not needed)

  • @kmcdo
    @kmcdo Před rokem +3

    First!

  • @FreddieDeetlefs
    @FreddieDeetlefs Před 8 měsíci

    This is pointless because if both ValueTypes take a string anyway, the mistake can still be made where a new UserId is constructed with the tenantId, and vice versa, so you just moving the issue up the pipeline, this does not solve anything

  • @GnomeEU
    @GnomeEU Před 7 měsíci +1

    We've NEVER had problems identifying the IDs in our projects i feel this is another over engineering that creates more problems than it solves. The more i learn about "advanced" software development the more i learn about problems that no one ever had.
    Why stop there, lets encapsulate every single property, then copy it at least 5 times. This solves a minor problem at best.
    Do these techniques just get pushed so someone can sell more books?

    • @md.redwanhossain6288
      @md.redwanhossain6288 Před měsícem

      This is not overengineering. EF Core 8 now officially supports this feature. If this is over-engineering, microsoft will not provide the feature in the first place.

  • @alan-
    @alan- Před 10 měsíci +2

    The codebase keeps changing between videos. If you're following along you'll struggle unless and even with being a patreon member and having access to the source code.

  • @smliwhcirdeirf
    @smliwhcirdeirf Před 11 měsíci +1

    For some reason I get this exception when I use the register endpoint.
    Mapster.CompileException: Error while compiling
    source=BuberDinner.Application.Authentication.Common.AuthenticationResult
    destination=BuberDinner.Contracts.Authentication.AuthenticationResponse
    type=Map
    ---> System.Reflection.AmbiguousMatchException: Ambiguous match found.
    ...

    • @GrimReaper160490
      @GrimReaper160490 Před 11 měsíci +2

      In the mapping configs, you can enforce .ToString() on the src.MenuId.Id.Value. This makes it work as it can parse the string value to a Guid just fine.

    • @felixnotthecat4249
      @felixnotthecat4249 Před 9 měsíci

      ​@@GrimReaper160490 Mate, thank you so much. You made may day. I even sent Amichai an email asking for help XD
      Thank you again.

  • @tienlx97
    @tienlx97 Před měsícem

    How about 1-1 relationship : Ex: 1PurchaseOrder can belong to 1 Vendor
    public class PurchaseOrder : AggregateRoot {
    public Vendor Vendor { get; private set; }
    public VendorId VendorId { get; private set; }
    }
    // Vendor
    builder.Property(p => p.VendorId)
    .HasConversion(
    id => id.Value,
    value => VendorId.Create(value));
    // TODO
    builder.HasOne(p => p.Vendor)
    .WithMany()
    .HasForeignKey(p => p.VendorId)
    .IsRequired();
    But I can not do this