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

Поділитися
Вставка
  • Опубліковано 26 лис 2024

КОМЕНТАРІ • 108

  • @ahmedrizk106
    @ahmedrizk106 Рік тому +27

    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 11 місяців тому +11

    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 10 місяців тому +2

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

    • @OldShoolGames
      @OldShoolGames 9 місяців тому +2

      Do you have any link to it ?

    • @yougayan
      @yougayan 6 місяців тому

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

  • @MilanJovanovicTech
    @MilanJovanovicTech Рік тому +23

    Good to see you back 🔥

    • @nawarali1912
      @nawarali1912 Рік тому +5

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

    • @alan-
      @alan- Рік тому +5

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

  • @poddev
    @poddev Рік тому +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  Рік тому

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

  • @alexandercarlsen2038
    @alexandercarlsen2038 Рік тому +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  Рік тому

      I like that approach as well

    • @matthewrossee
      @matthewrossee Рік тому

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

  • @shoooozzzz
    @shoooozzzz Рік тому +4

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

  • @Maxim.Shiryaev
    @Maxim.Shiryaev Рік тому +5

    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  Рік тому +1

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

  • @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.

  • @prashlovessamosa
    @prashlovessamosa Рік тому +7

    Long time pal.

  • @qaphuikpoh
    @qaphuikpoh Рік тому +3

    Good to see you back 🎉

  • @sphrtehrani
    @sphrtehrani Рік тому +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 Рік тому +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 7 місяців тому

      Use ULID based GUID

  • @MrDiscussion
    @MrDiscussion 4 місяці тому

    My thought process it that the problem is with EF, which is a tool for our infrastructure layer and therefore our domain should not be responsible for the solution of the problem but rather the infrastructure itself. Therefore for me, the most suitable solution of the ones you mentioned is the first one. This I think also makes it easier if in the future EF decide to support this to change the infrastructure layer.

  • @vagnerpadilha3485
    @vagnerpadilha3485 Рік тому +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  Рік тому +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 Рік тому +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  Рік тому

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

  • @davemasters
    @davemasters Рік тому

    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!

  • @atlesmelvr1997
    @atlesmelvr1997 Рік тому +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  Рік тому +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

  • @martinmagpantay4226
    @martinmagpantay4226 Рік тому

    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 Рік тому

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

  • @jose49716
    @jose49716 Рік тому +2

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

    • @amantinband
      @amantinband  Рік тому

      Thanks! Slides - Figma. Arrows - Presentify.

    • @jose49716
      @jose49716 Рік тому

      Thanks for letting me know!!! Great content.

  • @troelsmortensen9914
    @troelsmortensen9914 9 місяців тому

    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.

  • @WayneGreen-g8l
    @WayneGreen-g8l 9 місяців тому

    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).

  • @Codewrinkles
    @Codewrinkles Рік тому

    Nice one. Welcome back!

  • @evgeniysir4220
    @evgeniysir4220 Рік тому

    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 Рік тому

      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!

  • @mightybobka
    @mightybobka Рік тому +2

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

    •  Рік тому +1

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

  • @maxbitran
    @maxbitran Рік тому

    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.

  • @S3Kglitches
    @S3Kglitches Рік тому +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 Рік тому +1

      THANK YOU. Agree with you 100%

    • @amantinband
      @amantinband  Рік тому +1

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

    • @GreenDimka1
      @GreenDimka1 Рік тому

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

  • @jamesroot9777
    @jamesroot9777 Рік тому +1

    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 Рік тому

      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

    • @decton4461
      @decton4461 Місяць тому

      Did you find a solution yet 😂?

  • @blastermeteor9847
    @blastermeteor9847 9 місяців тому

    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

  • @radiosh66
    @radiosh66 Рік тому

    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  Рік тому

      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 Рік тому

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

  • @VahidRassouli
    @VahidRassouli Рік тому

    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 Рік тому

      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 7 місяців тому

      @@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.

  • @925082
    @925082 Рік тому

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

  • @alan-
    @alan- Рік тому +3

    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.

  • @LoZioIAR
    @LoZioIAR Рік тому

    Great!! You are back!!

  • @ahmad_9877
    @ahmad_9877 Рік тому +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 7 місяців тому

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

  • @PatrickImboden
    @PatrickImboden 21 день тому

    Thank you for your videos.
    Well I'll stick to my Grandfather method. I think it is much clearer. The thing is, many times I don't have just 1 database, I have many other Rest and old WCF services that I have to connect to get my data. These are objects that I don't have control of. Having mappings between my domain layers and my infrastructure objects is much easier. In my "real world scenario" my database only has one part of the total data needed to run the applications. The id's needed for those external systems are easier to just get mapped with automapper to my domain layer objects.

  • @ayalamac
    @ayalamac Рік тому

    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  Рік тому

      Presentify. ZoomIt doesn't work on MacOS 😢

  • @Eirenarch
    @Eirenarch Рік тому +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

  • @md.arafatrahmanrana242
    @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. 😊

  • @timschmidt5469
    @timschmidt5469 Рік тому

    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!

  • @craigmunday3707
    @craigmunday3707 7 місяців тому

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

  • @softwaretitbits5849
    @softwaretitbits5849 Рік тому +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.

  • @shakeuk
    @shakeuk Рік тому

    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  Рік тому

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

    • @mohamedal-qadeery6530
      @mohamedal-qadeery6530 Рік тому

      @@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 :(

  • @ahmedrizk106
    @ahmedrizk106 Рік тому

    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  Рік тому

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

    • @ahmedrizk106
      @ahmedrizk106 Рік тому +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  Рік тому +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

  • @ilyahryapko
    @ilyahryapko Рік тому

    2:00 Static method signature should probably return ReservationId?

  • @tplummer217
    @tplummer217 Рік тому

    Great stuff, thanks!

  • @jwbonnett
    @jwbonnett Рік тому

    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.

  • @DummyFace123
    @DummyFace123 Рік тому +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

  • @DJHightower77
    @DJHightower77 Рік тому

    The sound was very quiet in this video.

  • @svorskemattias
    @svorskemattias Рік тому

    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  Рік тому +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 Рік тому

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

    • @amantinband
      @amantinband  Рік тому +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 Рік тому

    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  Рік тому +2

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

    • @CodeBallast
      @CodeBallast Рік тому

      @@amantinband Keep up the good work, champ!

  • @M1stFink
    @M1stFink 7 місяців тому

    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 Рік тому

    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)

  • @Brendan2Alexander
    @Brendan2Alexander Рік тому +1

    I have strongly typed my cats

  • @kmcdo
    @kmcdo Рік тому +3

    First!

  • @FreddieDeetlefs
    @FreddieDeetlefs Рік тому

    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

  • @ricardomartinez1
    @ricardomartinez1 Рік тому

    “reasons”

  • @GnomeEU
    @GnomeEU Рік тому +3

    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 7 місяців тому

      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.

    •  5 місяців тому

      I agree that this is not an usual problem to have in small teams, maybe the problem of confusing arguments could happen in a project where hundreds of people work on the same code base and they don't know each other or it's a community driven open source project. In any case, you will have other mechanisms to detect this error: unit tests, integration tests, manual tests, etc tests... code reviews, peer reviews, and some tools might give you hints that the names do not match... so I would only use the technique of having value objects for Ids when it's absolutely necessary.
      In other languages like Typescript this would make more sense....

  • @smliwhcirdeirf
    @smliwhcirdeirf Рік тому +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 Рік тому +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 Рік тому

      ​@@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 7 місяців тому

    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

  • @baulron
    @baulron 8 місяців тому

    Amazing content. Thank you!