Avoid This Common Mistake in DDD Modeling

Поділитися
Вставка
  • Опубліковано 8 чер 2024
  • Become a sponsor to access source code ► / zoranhorvat
    Join Discord server with topics on C# ► codinghelmet.com/go/discord
    Enroll course Beginning Object-Oriented Programming with C# ► codinghelmet.com/go/beginning...
    Subscribe ► / @zoran-horvat
    Watch Domain-Driven Design Fundamentals course by Steve Smith and Julie Lerman at Pluralsight app.pluralsight.com/library/c...
    Watch Eric Evans on the Entity Equality Methods at Pluralsight ► app.pluralsight.com/ilx/video...
    If you are applying the Domain-Driven Design, you implement entities and value objects all day. If so, you must have encountered this situation: Almost every video, article, or online resource will teach you to override Equals and GetHashCode in an entity in such a way as to proclaim two entities equal if their identities are equal.
    This may be a surprise if you haven't heard it before, but that implementation is wrong. If you are guilty of it, as so many programmers are, it is the perfect time to start removing that bug from your code.
    This video explains why comparing two entities for equality is impossible, let alone only comparing their IDs and ignoring other attributes.
    By the time you finish watching this video, you will already start feeling better about DDD.
    00:00 Intro
    00:37 The wrong Entity base
    03:11 Understanding the mistake
    04:55 Why not Equals?
    06:45 The correct Entity base
    09:56 Outro
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    ⭐ CONNECT WITH ME 📱👨
    🌐Become a patron ► / zoranhorvat
    🌐Buy me a Coffee ► ko-fi.com/zoranhorvat
    🗳 Pluralsight Courses ► codinghelmet.com/go/pluralsight
    📸 Udemy Courses ► codinghelmet.com/go/udemy
    📸 Join me on Twitter ► / zoranh75
    🌐 Read my Articles ► codinghelmet.com/articles
    📸 Join me on LinkedIn ► / zoran-horvat
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    👨 About Me 👨
    Hi, I’m Zoran, I have more than 20 years of experience as a software developer, architect, team lead, and more. I have been programming in C# since its inception in the early 2000s. Since 2017 I have started publishing professional video courses at Pluralsight and Udemy and by this point, there are over 100 hours of the highest-quality videos you can watch on those platforms. On my UA-cam channel, you can find shorter video forms focused on clarifying practical issues in coding, design, and architecture of .NET applications.❤️
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    ⚡️RIGHT NOTICE:
    The Copyright Laws of the United States recognize a “fair use” of copyrighted content. Section 107 of the U.S. Copyright Act states: “Notwithstanding the provisions of sections 106 and 106A, the fair use of a copyrighted work, including such use by reproduction in copies or phono records or by any other means specified by that section, for purposes such as criticism, comment, news reporting, teaching (including multiple copies for classroom use), scholarship, or research, is not an infringement of copyright." This video and our youtube channel, in general, may contain certain copyrighted works that were not specifically authorized to be used by the copyright holder(s), but which we believe in good faith are protected by federal law and the Fair use doctrine for one or more of the reasons noted above.
    #csharp #dotnet #domaindrivendesign
  • Наука та технологія

КОМЕНТАРІ • 46

  • @zebcode
    @zebcode 4 місяці тому +10

    Literally 2 seconds in and you explain strongly typed Ids like it's nothing... yet that was the first lightbulb moment for me haha.

  • @fifty-plus
    @fifty-plus 4 місяці тому +6

    Another excellent video highlighting a common misconception. Nice one.

  • @furious385
    @furious385 3 місяці тому +2

    just another masterpiece from the maestro.

  • @henry-js
    @henry-js 4 місяці тому +2

    Love your videos! I don't get many opportunities to implement DDD techniques as I'm still a junior working on a legacy codebase

  • @TheHegi
    @TheHegi 4 місяці тому +1

    I have to say: Fantastic content! Thank you, sir and keep it up!

  • @Menicoification
    @Menicoification 4 місяці тому +6

    At first I didn't really like your videos because of how you talk 😅, but the content is so damn good, that I've watched them anyways and now I'm a fan.
    Keep it up!

    • @zoran-horvat
      @zoran-horvat  4 місяці тому +2

      Hey, how do I talk? What bothered you?

    • @Menicoification
      @Menicoification 4 місяці тому +1

      @@zoran-horvatI don't know the english term for it. But its the variety in tone and speed that bothered me at first. It just didn't sound "right / authentic" to me. Variety is good thing. But to me it sounded like a bit too much. I guess it's just personal preference.
      Anyhow. The content is just great stuff!

    • @zoran-horvat
      @zoran-horvat  4 місяці тому +1

      @@Menicoification Thanks!

    • @theyur
      @theyur 4 місяці тому +16

      I have opposite experience. When I first heard Zoran and his manner of speaking, to me it was like storytelling about c# - totally different from academic-kind materials :)

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

      ​@@zoran-horvatHey, just another opinion about that - In my case I think the way you talk and deliver information is good for educational videos. I obviously don't know how you talk in real life 😅, but in videos that speech speed, clarity and tones greatly helps to place accents on some stuff and to catch and understand information better)
      P.S.
      Thanks for your work and great content!
      I always recommend my students to watch your channel(unfortunately I doubt they do it...)

  • @AndersBaumann
    @AndersBaumann 4 місяці тому +2

    Hi Zoran.
    Are you using the Book example together with NHibernate or EF? If so, isn't there a problematic corner case where you could compare a Book and a BookProxy in the x.GetType() == y.GetType() part of IdEqualityComparer?

    • @zoran-horvat
      @zoran-horvat  4 місяці тому

      I don't use NHibernate. EF Core uses comparison on keys to determine which entities it is already tracking, so it doesn't depend on any custom comparisons.
      However, your note on type comparison and proxies is valid. There is a coding pattern that solves it, and you can see it in generated code for records, where the compiler adds a virtual method which returns the comparison type. It would be GetType in any class, but a proxy would only forward the call and hence the comparison type would remain unchanged, preserving the equivalence relationship. That is a very interesting solution.

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

      So don't you need to implement this pattern in your IdEqualityComparer for this to work in a EF Core context? Or do I misunderstand you?

    • @zoran-horvat
      @zoran-horvat  4 місяці тому

      @@AndersBaumann No, EF Core doesn't call any of these, not does it use proxies. When registering an entity type with EF Core, you must specify one or more properties that comprise the key. EF Core then applies its custom logic to index the entities by the keys, so there is nothing for you to do to make it work. EF Core calls that process identity resolution and it is entirely implemented inside of EF.

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

      You misunderstand me. I am talking about this, maybe a bit contrived example, but I hope you get my point:
      var guid = Guid.NewGuid();
      var bookId = new BookId(guid);
      var book = new Book(bookId);
      using var dbContext = new YourDbContext();
      dbContext.Books.Add(book);
      dbContext.SaveChanges();
      var theSameBook = dbContext.Books.FirstOrDefault(book => book.Id == guid);
      var isItTheSameBook = book == theSameBook; // -> false because book is of type Book and theSameBook is of type BookProxy.

    • @zoran-horvat
      @zoran-horvat  4 місяці тому +3

      @@AndersBaumann I see your point. You are right, EF Core is using proxies in lazy loading and in that case the comparison would fail. My solution should be augmented so that the entity exposes a virtual method, say EqualityContext, which just calls GetType. Since the process would not modify the behavior of this method, the type used in comparison would be the same in the entity and its proxy, and so the equality composter would then support proxies as well.
      Thank you for this analysis. I will keep it for some later video!

  • @David-id6jw
    @David-id6jw 4 місяці тому

    @4:30 - Not a book, but I have a "user" entity (keeping it simple), and a vote that the user made. So there can be a Dictionary, where that's a lookup of the vote made by the user (though the vote is likely more complex than a string, so would have its own object). The user has identity stuff, but it would most likely have the username as the Id, not a Guid. Probably. And then you can have a HashSet to contain a list of all users who voted as you process the votes.
    Is the User who made a vote in post 10 the same as the user who made a vote in post 20? If you compare by user name, then yes, so you can deprecate the old vote and replace it with the new vote. The main difference, I guess, is that I'm using some content of the entity to identify it, and not some abstract guid or whatever. It would be a bit like using the book title as the ID instead of the guid (except book titles aren't guaranteed to be unique, while usernames on a forum are). Or, going the other direction, saying that the User object needed to have an ID, which would be different for the vote made in post 10 vs the one made in post 20.
    But having (multiple) equality comparers built in that handle different types of comparisons is a great idea. Maybe you want to track both votes because you may need to reference either one of them as you work through the dataset, so you want to compare by username+post number when adding to the dictionary (though you only use the last post by each username when you generate the final results). So have an equality comparer for just the username (used by the HashSet), and an equality comparer for username+post (used by the Dictionary). Doesn't lend itself to an abstract base class, though.
    Further, you have to be able to invert the relationship, where you have a Dictionary, to figure out who voted for what, which also lends itself to equality comparers on the Vote which may need to be done in multiple different ways (such as whether or not it should be case sensitive). And in this case, a guid ID for each vote that was found would be meaningless for comparison purposes.
    Anyway, I think I basically agree with your point in the video, I just objected to the idea that there is no reason for those types of data structures to need some type of equality comparison. Or perhaps it's solely about having a guid as the basis for a comparison, which implicitly links it to database work. That assumption seems to carry the baggage that the ID _is_ the object, at a certain level, so if you get rid of the database association, that kind of falls away as well.

    • @zoran-horvat
      @zoran-horvat  4 місяці тому +7

      Why dictionary of user and not dictionary of user ID? I mean, that is what you are doing.
      You can find an excellent example of how Microsoft handled the same situation in the Entity Framework. EF uses what they call identity resolution to identify entities returned from subsequent queries as same (same, not equal!). Here is the specification:
      learn.microsoft.com/en-us/ef/core/change-tracking/identity-resolution

  • @dave_s_vids
    @dave_s_vids 3 місяці тому +1

    Zoran I just wanted to comment to say that I find your videos very useful and it would be great if you added a Patreon tier for "just saying thanks" for £2/month where you don't get anything additional, but want to show your appreciation anyway :)

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

    You need to override equals when syncing records form one database to another to check if source records need to be updated in the target database.

    • @zoran-horvat
      @zoran-horvat  4 місяці тому

      No, you don't. How come that database synchronization of entities is the responsibility of the entity? That doesn't make any sense.
      Now that I said it, I expect you to come up with a solution that better separates those two responsibilities. So, what is your solution?

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

      ​@@zoran-horvat I work with dynamics and i have a service that retrieves records periodically from one dynamics environment into another environment. How can I tell that the record was already synced and therefore skipping this record? With overwriting equals on the class in combination with a record property with the attributes to compare, I have saved tons of code since dynamics itself overwrites the equals method for its own classes like optionset and entityreference. Therefore I overwrite the equals method on the class to compare the record property (and not the class) so I can use methods like ienumerable1_class.except(ienumerable2_class). A solution is to automatically push a change instead of pull but this is much worse in dynamics since it would need to execute code on every attribute.

  • @C00l-Game-Dev
    @C00l-Game-Dev 2 місяці тому

    As a unity dev, I've been looking into Data Oriented Design, and was wondering if you could do a video on DOD.

    • @zoran-horvat
      @zoran-horvat  2 місяці тому

      That is a narrow topic which I don't plan to get into in the near future.

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

    only thing i got from this video, some codes are useless until you know the use at given point of time.

  • @wiliam.buzatto
    @wiliam.buzatto 4 місяці тому

    Hey Zoran, Would like to exchange an idea.
    What about entities that are the same and have multiple registration by software design. For example: In the Family Tree web site, different users can register the same person: I and my distant relatives have register our great-great-grandmother in the site, so there is lots of Entities that are indeed the same. The website recognizes it and suggest a merge.
    Great Content!

    • @zoran-horvat
      @zoran-horvat  4 місяці тому +1

      Then you model it that way. Not that is not the same as comparing the IDs of two entities.
      The problem you mentioned is present in authentication, too. Many systems allow merging of user accounts, but that operation is complicated, sensitive, and even dangerous from the security standpoint. No wonder developers so often flat out refuse to develop it, and let the company handle their end users by talking to them.

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

    I've never done anything related to equality comparison but to compare two instance of that entity type to find out, for example, what the best "score" is (if we take game scores as an example). Being equal, greater or lower should only be applied with something that make sence. Clearly, if you ever get twice the same entity within the same comparison, the issue is a bug.
    Thanks for the tips on the Strongly typed ID though. I will implement that so make it more clear and avoid bugs i could cause on a friday's night lol.
    I like to use uint or ulong for primary keys, since why on earth would it be negative. But for some reason EFCore migration cannot infer the type convertion (even with int). I need to do something like this:
    public readonly record struct JobID(uint Id)
    {
    public static readonly JobID None = new(0);
    }
    }
    p_ModelBuilder.Entity()
    .Property(p_E => p_E.Id)
    .HasConversion(
    p_JobID => p_JobID.Id,
    p_Id => new Job.JobID(p_Id));
    Any idea why? Since you never mentionned it in your past video, i thought there was never the need to do this.

    • @zoran-horvat
      @zoran-horvat  4 місяці тому +2

      I am preparing a video that explains the use of strongly typed IDs with the Entity Framework. That video will explain the need for conversion and a few other things, such as application-generated IDs, clusters indexes, nullable IDs with conversions, and similar. It will be a very detailed demo, and that is why I haven't completed it yet.
      Your note about uint and ulong is interesting. There is a strong case for negative IDs! It is common to set autoincrement ID to start from 1 and to let 0 indicate an unassigned value. But if the application needs to know some pre-existing objects and to guarantee that they exist, then you insert them with fixed negative IDs and keep those values in the application as named constraints. That is a typical application of Null Object and Special Case patterns, persisted variant.

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

      @@zoran-horvat Oh nice! Yes, in my case the non defined IDs always was 0. It's the default behavior that was defined anyway. So with uint it used to make sence for me and provided more room if there was the need for that much quatity of object. But honestly, i just prefer to use types that makes sence, and negative IDs in the database itself didn't.
      So undefined objects just ship with 0 and will get auto incremented by the database, no issue with that i assume.
      But that indeed doesn't allow you to have multiple initial value for your strongly type ids, i don't see the point of it though X)

  • @pyce.
    @pyce. 3 місяці тому

    You might want to store entities in collections that are read optimized, no?

    • @zoran-horvat
      @zoran-horvat  3 місяці тому

      Define read and define optimized. Both terms are slippery in the sense that there is no single definition of them.

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

      @@zoran-horvat I assume that this was said in defense of using dictionaries to read values by key

    • @zoran-horvat
      @zoran-horvat  Місяць тому

      @@timur2887 And so I ask what makes you read entities by key, how often do you for that and why?
      On top of my head I cannot remember such a case, so it must have been years ago when I did it the last time, or it was an exceptional corner case, whatever.

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

      @@zoran-horvat perhaps there are rare cases when it is useful to lookup dictionary values as an intersection of another set of entities` keys

    • @zoran-horvat
      @zoran-horvat  Місяць тому

      @@timur2887 I was asking for a case, not for guesswork. Do you have an example from your practice?

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

    I would create those Dictionary and hashset to make my colleges to drive crazy, and either to make their hair gray or loose then all 🙂 Like 10 years ago I run into this problem, I was debugging a mathematician's guy code 🙂 They could not fix in in 2 months 😀

  • @bogdanpavlovic3456
    @bogdanpavlovic3456 4 місяці тому +2

    🍺🍻🍺