Learn To Love DDD-Style Strongly Typed IDs

Поділитися
Вставка
  • Опубліковано 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
    There are many implementations of strongly typed ID types on the Internet, but not many satisfy all of the mandatory and nice-to-have traits we expect from such a class. An ID, custom or built-in, must be an immutable value object with full value-typed semantics, supported by the underlying database and the intermediaries such as ORMs and micro-ORMs; it would be nice if it caused no additional allocations and garbage collection overhead.
    This video will help you understand the steps required to design a small but powerful custom ID type that satisfies all functional and performance considerations that define a good identity.
    An identity is essential to any object we persist. It spans the time between application runs and the time between creation and a later retrieval of the same persisted object. Hence, it exists in all persisted entities.
    In this video, we start with autoincrement IDs and GUIDs, finding their ultimate deficiency - an ability to generate application bugs. Then, we introduce strongly-typed custom IDs that are application-generated and backed by compile-time assignment checks.
    Before this short video ends, you will learn to love the simplicity, safety, and even speed of your custom identity types that are powerful, compile-time safe, and fully supported by Entity Framework Core and relational databases.
    00:00 Intro
    01:15 Autoincrement and GUID IDs
    02:54 The Failure Caused by Using GUIDs
    04:53 Developing a Custom ID Type
    08:53 Outro
    Thank you so much for watching! Please like, comment & share this video as it helps me a ton!! Don't forget to subscribe to my channel for more amazing videos and make sure to hit the bell icon to never miss any updates.🔥❤️
    ✅🔔 Become a patron ► / zoranhorvat
    ✅🔔 Subscribe ► / @zoran-horvat
    ⭐ Learn more from video courses:
    Beginning Object-oriented Programming with C# ► codinghelmet.com/go/beginning...
    ⭐ Collections and Generics in C# ► codinghelmet.com/go/collectio...
    ⭐ Making Your C# Code More Object-oriented ► codinghelmet.com/go/making-yo...
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    ⭐ 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 #ddd
  • Наука та технологія

КОМЕНТАРІ • 153

  • @heintaljaard8210
    @heintaljaard8210 3 місяці тому +10

    Apart from the content, which is always great, Zoran’s commentary and humour crack me up😂
    Thanks for going into WHY strongly typed IDs are good

  • @andersjuul8310
    @andersjuul8310 4 місяці тому +23

    The advice bears repeating -- thanks for the continous flow of golden nuggets

  • @MacHmura
    @MacHmura 4 місяці тому +27

    Really enjoying your new style of videos, thanks!

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

      What is the new style, what did I change? If it's good, I want to do more of it!

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

      @@zoran-horvat You didn't show your face during the video 😄 But that could not be the reason^^

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

      @@zoran-horvat I think it's just the general vibe to be honest :) it used to feel like a nice, laid-back lecture (and it was great), and now it feels smooth - the code appears swiftly and all the details and comments are displayed next to it.
      Both formats are great, but this one just feels fresh
      Keep posting please :)

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

      @@MC_DarkMaster haha, cant be that ;)

  • @alexhall840
    @alexhall840 4 місяці тому +7

    Amazing story telling and practical programming techniques that work

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

    Nice format, and great lesson. Thank you!

  • @nicklaspillay7923
    @nicklaspillay7923 4 місяці тому +3

    Really engaging video, genuinely a fun watch! Nice stuff!

  • @leos-clockworks335
    @leos-clockworks335 4 місяці тому +3

    Great video as always.
    This was pretty much my journey with my product.
    I started off with some uint for the ID, than we decided to change it into a string and back into an int, I got fed up with changing the ID all over the project everything management decided to change things around -> So I wrapped it in a class UserIdentifier. That in time turned into a struct, and after your video about records I changed it into a record, which removed quite a bit of code from the Equality functions. It's a shame but I cannot use record structs in this project, so might have to revert it back to a struct.
    I really enjoy your videos and learn a lot from them, thank you!

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

      I know the path you walked because that happened to me, too. Some critics don't realize that everything I say, I have pushed to production first at some time during my career. All this code just works.

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

    Wonderful explanation, thank you kindly!

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

    Derived types (like in Ada) would be really useful for this purpose. They would be essentially just an int, only the compiler would tag
    them with additional type info, treating them as totally distinct types.

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

      A wrapper type essentially behaves like a derived type.

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

    Sounds like a good-old D&D tale)

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

    Really clever solution 👍🏻

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

    This is excellent!

  • @hectorkrionas-lamprou4922
    @hectorkrionas-lamprou4922 4 місяці тому +1

    Interesting. I was wondering what would be the difference in using a readonly struct instead of a readonly record struct

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

    Top stuff, makes sense...

  • @michaelwong1908
    @michaelwong1908 3 місяці тому

    I understand that for different entities, I would need to create different strongly typed Ids. So it means also to declare the Empty properties and NewId(). Any way to avoid rewriting each time? I guess compiler generated codes?

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

    Hi Zoran. Great video! What about when you have a DataTransferObject, say BookDto, in your API?

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

      The DTOs should contain primitive types, because their purpose is to transfer low-definition data. Therefore, the conversion to and from a DTO would also include unwrapping and wrapping the GUID into a strongly typed ID.
      This process is the same as for all other pieces of data in the DTO and it stems from the fact that the domain model is built around processes and DTOs are built around values. Putting both aspects into the domain model would cause lots of trouble everywhere, and hence we separate those.

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

    Love it !

  • @AlBex-dc6yq
    @AlBex-dc6yq 2 місяці тому

    I included a F# project into my C# solutions with one simpe module like this ...
    module IdDefinitions
    open System
    type BookId = BookId of Guid
    type PublisherId = PublisherId of string
    type AuthorId = AuthorId of int
    ... But it requires more effort where everybody else asumes basic types (JSON, loggign, etc.). You simply can not explain the benefits to others, until they run into problems that you had 10 years ago already.

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

      Strongly typed IDs are part of the model, and therefore not suitable for serialisation. A rule of thumb is to perform serialization/deserialization using DTOs, and then convert them to and from the domain models, because models serve different purposes and usually hide their inner representation or use representation that suits their own responsibilities. That means the DTO should contain a plain ID, which is again converted from and to a strongly typed ID.

    • @AlBex-dc6yq
      @AlBex-dc6yq 2 місяці тому +1

      ​@@zoran-horvat It's a rule of thumb - absolutely. In the end, one always have to decide where the focus lies and have to realize that there is no right or wrong way to do something, only sensible and senseless decisions.

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

      @@AlBex-dc6yq Precisely. You just try to model around obstacles. Every decision depends on the circumstances.

  • @xxfrikoxx
    @xxfrikoxx 2 місяці тому

    Very good! I would however add to this video a section about serialization problems (Your key will be serialized as an object by default) and other reasons why you should use a library for doing this.

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

      You should not burden the domain model with serialization. That should be the ability of the corresponding DTOs, and DTOs, conversely, should not burden with such domain problems as stringly-typed IDs. They should only contain primitive values, such as GUID, int, or long for the ID.

  • @pinguincoder
    @pinguincoder 3 місяці тому

    Thats what Automated Tests are for so even if you are passing the wrong Id you should have an automated test asserting the expected outcome

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

      Two questions: 1) Who's gonna automate that? 2) Do you tend to stringify models and leave their correctness to automated tests, with respect to question 1?

    • @pinguincoder
      @pinguincoder 3 місяці тому

      1. The Developer that writes the Feature.
      2. Im with you on the DDD approach of introducing specific types rather than falling victim to primitive Obsession though. Just wanted to Point Out that If there is a Bug because someone passed the wrong value that should usually be caught by a unit or Integrationtest.

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

      @@pinguincoder That's fine, so let's focus on point 1. There is one place to define a strong type and dozens where the assignments happen. You are advocating weak assignment checks, dismissing the most powerful static code analysis tool we have - the compiler - and request, say, writing 20 unit tests, plus 2 which you forget, as a better idea? Be so kind to explain to me the economy of that, excuse my language, lunacy.

  • @user-zq4ch2hp5u
    @user-zq4ch2hp5u 4 місяці тому +1

    You could pass the object instead of the ID, you then don't have to overengineer a solution, as you already then have type safety.
    In DDD it is common practice to pass around objects and not Ids, this way you can perform other DDD functions whereas before you are limited to an ID.

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

      I'm not following you. What object do you mean instead of the ID?
      Don't forget that any type you use as the identity must map to a primitive type supported by the database.

    • @djdaveydave123
      @djdaveydave123 3 місяці тому

      MakeFamous(Book book)

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

      @@djdaveydave123 Where did you get the Book object from?

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

    I assume that this impacts JSON serialization and could make a project upgrade from Guid to record structs a bit risky if serialization is used.

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

      In what way does it make it more risky?
      I expect certain complications in serialization, mostly coming from syntax, but not from any ambiguity that could cause risks.

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

      ​@@zoran-horvat Thank you for the reply and for the videos, always apreciated. Since this is not a transparent in-place update, care should be taken to test areas of the code where serialization/deserialization is used since the compiler cannot check for problems. One example is public API where responsens could be objects that use automatic mappers. If tests are present this should be easy to catch.

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

      @@mihaiga I see your point. I'll keep it in the back of my mind for a while. Maybe something will come out.

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

      Maybe there could be a way to interpret the wrapped GUID struct as a GUID when it comes to serialization? Maybe an implicit conversion could be used from GUID to the new strongly typed identifier?

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

    How do you go about removing the exception that is thrown when you use entity.HasKey(e => e.Id); entity.HasComplexProperty(e => e.Id);? It appears EF doesn't support complex PK's although it looks like you have at least one in your example.

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

      There must be the value mapping on that property. It works well with EF, I'll prepare a separate video for that.

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

      Excellent. Many thanks.

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

    How would this mirror to the database? Wouldn't it generate a separate table for the BookIds?

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

      No, there must be the value mapping back to plain GUID and that would be the row key in the database.

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

    Hi Zoran, great content as always.
    What about making it generic?
    public readonly record struct StronglyTypedId(Guid Value)
    where T : notnull
    {
    public static StronglyTypedId Empty => new(Guid.Empty);
    public static StronglyTypedId New => new(Guid.NewGuid());
    public override string ToString()
    {
    return Value.ToString();
    }
    }

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

      What would T be? Any class?

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

      I see your point with this type, but I object that it is very abstract for the purpose. Using it almost looks like Yoda speak. If it is to save three lines of code per entity class, I'm not sure it is worth the mental effort associated with its use.
      Anyway, on first look, I'd say it would work fine.

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

      @@edgeofsanitysevensix I think T is the class where the Id is for. So in the Book class you would define a StronglyTypedId Id ...

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

    A readonly record struct is a thing? Hmn, that could prevent some Heap churn.

  • @thygrrr
    @thygrrr 7 днів тому

    / *reads Bertrand Meyer, the Book* /

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

    isn't there an allocation for GUID? aren't you making a complicated wrapper for what is essentially an enum (yea i know it's not the same, but it's only the same because you cant define enums at runtime which would only be useful if you dont know all the enums at compile time)?

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

      Guid is a value type. There is no allocation for it.

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

      @@zoran-horvat oh, ok, thats interesting

  • @gbsyi_
    @gbsyi_ 3 місяці тому

    Interesting. But what if I need to place Books and Publishers to different APIs? Books aggregate and Publisher aggregate will be in different projects. And then it'll be difficult to use PublisherId in Books aggregate. I need to declare that type again.

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

      Both APIs should contain the types they are using.

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

    Interesting idea -- what I get is I need to learn about records, since they don't exist in Java or C++ and I hadn't come across them in C# before -- so until now "record" was just what Pascal call a "struct" to me. Not that I have much immediate practical use for this.

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

      There is a previous video I made that explains C# records and their use in depth.

  • @MrNickP
    @MrNickP 3 місяці тому

    When my class is serialized to JSON I am getting Id : { 'Value' : 1} instead of Id : 1. Is there a way to fix this?

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

      Strongly typed IDs are like any other type when it comes to serialization. You have to unpack the primitive values before serialization and package them back after deserialization.
      The problem you are experiencing probably originates in the attempt to serialize a domain model, which is a wrong step in any settings. You should serialize DTOs instead.

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

    Wouldn't the Empty property bites us later on, given it could produce an object in an invalid state. I'm thinking of one of your previous lessons on never construct an object in an invalid state.

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

      It is not invalid for an ID. That is a regular case when you request the database to populate the ID upon insert, and it is backed by EF configuration. On the other hand, if you set the ID value in the entity, EF will use that in the insert and not change it.

  • @user-ju2hn6zm6u
    @user-ju2hn6zm6u 4 місяці тому

    It's worth mentioning that guid created from c# application and used as primary key clustered index in relational database significantly decrease performance.

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

      Yes, that is why the index should not be clustered in that case.

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

    Could you make a video about Rules Engine Pattern? And envolve it with Specification Pattern

  • @FroodyBanana
    @FroodyBanana 3 місяці тому

    public readonly record struct BookId(Guid Value):
    > Model bound complex types must not be abstract or value types and must have a parameterless constructor. Record types must have a single primary constructor. Alternatively, give the 'Value' parameter a non-null default value.
    Ok apparently entity framework doesn't do that, let's just use a record then
    public record BookId(Guid Value):
    > InvalidOperationException: The entity type 'Book' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'.
    ....
    Ok, so just ignore the whole strongly typed ID thing and use int Id, got it.

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

      Wrong.

    • @FroodyBanana
      @FroodyBanana 3 місяці тому

      ​@@zoran-horvat
      Previous was with a Sqlite db, so now I made it as simple as I possibly could. Memory database, and copy straight from the video, no trying to work it into my existing project.
      #1.
      New WebApp, .NET 8.0
      Add nuget EFCore.InMemory and dependencies
      #2.
      builder.Services.AddDbContext(options => options.UseInMemoryDatabase("Test"));
      #3.
      public class MemoryContext : DbContext {
      public MemoryContext(DbContextOptions options) : base(options) { }
      public DbSet Books { get; set; }
      }
      public class Book {
      public readonly record struct BookId(Guid Value);
      public BookId Id { get; set; } = new(Guid.Empty);
      public string Title { get; set; } = string.Empty;
      private Book() { }
      }
      #4. Index page
      public void OnGet() {
      var books = _context.Books.ToList();
      }
      # result:
      InvalidOperationException: Property 'Book.Id' cannot be used as a key because it has type 'BookId' which does not implement 'IComparable', 'IComparable' or 'IStructuralComparable'. Use 'HasConversion' in 'OnModelCreating' to wrap 'BookId' with a type that can be compared.
      There must be steps in the video that are being left out? Such as defining keys inside OnModelCreating?

    • @FroodyBanana
      @FroodyBanana 3 місяці тому

      @@zoran-horvat
      UA-cam eats my code snips so I'll just say I fixed it by first rewriting away from sqlite to inmemory, and then figuring that inmemory has its own issues so I had to write a value converter for the key to make it work.
      And because that became tedious very fast I wrote a generic identity type that I can just use on all the entities and then a modelbuilder extension hack to put in a converter easily.
      It's ugly but it works. Sort of.
      Why is code just problems on top of problems on top of more problems?

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

    I'm 100% with you on that topic. The only thing I dislike is that if you want to export a model with System.Text.Json you will get an "ugly" Json from the Json-perspective because of unnecessary nesting. Should I then make a copy of that model only for Json because of that one field?

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

      That is a good question. I might try to come up with a good answer at some time later.
      Normally,, I avoid direct serialization/deserialization of domain models because that process would be ridden with issues anyway. I usually add an intermediate step, converting the model object into a DTO first, to make it serialization-friendly, among other things.

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

      Add a custom JsonCoverter for this Type.

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

      @@tobias989 It is not just the ID. Serializing a domain model, whose primary reason to exist is to model business processes, will always be connected to issues of all kinds. We normally use DTOs to support data-only operations and isolate the model from those duties.

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

      @@zoran-horvatThat is a good point. The DTO can contain directly the GUID or a String representing the BookId. With this approach no custom JsonConverter is needed.

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

      @@tobias989 The Json-Converter is no option for our projects because it's incompatible with source generation (trimming, aot, etc.).
      If our domain models are acceptable to serialize then it feels so unnecessary to introduce duplicate code only because we want to introduce strong id's

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

    Nice video! However, I wonder if this is not too much overengineering for such a small problem. Extrapolating the idea, why not creating strongly types for every other property of the Book and Author objects which are primitives and where have the same risk of passing an incorrect value?

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

      why extrapolating? 😊

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

      I'd say that you can push this idea to any length, and then it comes to a judgment when to stop. IDs are pervasive in entities design - every entity has one.
      If you knew the Web application I added side by side with this isolated demo application, you would know that turning only the three IDs into strongly typed IDs caused changes in 20-30 other places in the model and UI.
      Even in the smallest of all applications, with only two web pages, every ID is used in a dozen of places to render those two pages! You can imagine how many thousands of instances that would be in a large application. That makes this particular detail a highly cost-effective candidate for refactoring, a much better one than any other part of the design.

  • @auronedgevicks7739
    @auronedgevicks7739 3 місяці тому

    so ultimately what's the advantage of a strongly typed guid other than not making an obvious error?

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

      What is the advantage of using strongly typed anything in your code base other than not making an obvious error?
      Why use a compiled language other than letting the compiler report obvious errors?

    • @auronedgevicks7739
      @auronedgevicks7739 3 місяці тому

      @@zoran-horvatWe can't and shouldn't strongly type everything, only strongly type the things that matter. We use strings instead of enums in many places in our code because not all strings need to be strongly typed. We weigh the effort vs the benefit first. Saying everything should be strongly typed "just because" is not a good reason to do it. Efficiency isn't just in our code, it's the time spent doing something vs the benefit of the said thing. I was curious if there was any other reason to do this and the answer seems to be no.

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

      @@auronedgevicks7739 We don't use strings instead of enums, though I can imagine some programmers do.

    • @auronedgevicks7739
      @auronedgevicks7739 3 місяці тому

      @@zoran-horvatbut we do because even though a string may only contain a set of known values there's no reason to make them enums unless they need to be.

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

      @@auronedgevicks7739 I guess you do. It's not clear who you refer to as "we", though. Those I worked with in the last 10+ years are not your "we", I know that with confidence.

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

    @2:47 I paused for a moment here to ponder, why wouldn't we just create an Author, Book tuple table / entity? I often get stuck on things and can't move on. So I'm posting my question now and I will try to answer it myself in a reply later.

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

      Okay, so I think the answer is that for the purposes of this demo it doesn't matter. If you're stuck here get passed it. Yes you should probably have a Tuple table. Just keep watching and it will make sense.
      It's about strongly typing IDs which can help to reduce runtime issues that the compiler won't prevent.

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

      Right but now I'm wondering... Does Book ID become an entity? Hence have it's own table?

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

    Does it work with entity framework and mssql?

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

      It works with EF Care on SQL Server. I suppose that would make it work with MySql, too.

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

      @@zoran-horvat Gonna need to test this out! I see this as an really good solution for generic apprach to diferent types of ID's!

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

    I did a someid(int Value) and it said could not be mapped because the database provider does not support this type. (Sql Server)
    Greate idea though.

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

      There must be a value mapping for that to work. I will publish the next video soon, showing how strongly typed IDs work with EF Core and SQL Server. They work fine.

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

      @@zoran-horvatAwesome! Looking forward to it.

  • @alexeybeloushko7240
    @alexeybeloushko7240 2 місяці тому

    I dont get it. Id mistakes happen in intermediate layer mostly. Strong id types protects from making mistakes in domain layer, but it still posible to make DomainAClass.(DomainAClassID(DtoBClass.ID)) mistake in binder )

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

      The next video which is about to go live shows binding, too.

    • @alexeybeloushko7240
      @alexeybeloushko7240 2 місяці тому

      @@zoran-horvat and what if specification of GUID changes ? whats a point to make non template identity entity. you probably want to test domain run on different identity system.
      ddd on c# sucks.. crappy language

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

      @@alexeybeloushko7240 Are you more arrogant or wrong?

    • @alexeybeloushko7240
      @alexeybeloushko7240 2 місяці тому

      @@zoran-horvat about what? be more specific

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

    how to push this concept down to api?

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

      Through DTOs that remove mappings induced by the model, including those on IDs. A DTO would contain a plain GUID again, the same way as the database and UI already do.

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

      this is exactly what I don't want. how to make request from client to not mix ids.

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

      @@Kubkochan Invent a new JSON format. That is easy.

  • @adambickford8720
    @adambickford8720 4 місяці тому +5

    Perfect example of "the juice isn't worth the squeeze" that pushed me away from OO/DDD.
    Yes, you are solving a real problem, but the cost of the solution is higher than the cost of the problem. Not just the cognitive load, but now i'll almost certainly need adaptors, generics, etc to use this. Just like I can pass 'first name' (a string) into a function that is meant for formatting 'last name', its not worth creating FirstName and LastName types to solve that problem.
    The more pragmatic solution is If you really need to restrict it to the type, pass in the whole object and not just the id.

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

      There is nothing to add to your code base after this. The solution is complete, there is absolutely nothing to add to it. No adapters, generics, nothing. Check it out yourself.

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

      Why would you pass the whole object just for one property ? This is not recommended anywhere.

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

      @@andreibicu5592 What is the "whole object" and what do you mean by "pass the whole object"? If you are referring to the strongly typed ID, it is a value, implemented as a struct. It is passed around the same way as you pass a GUID or an int, and it is an object to the same extent. It is just a value.

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

      ​@@zoran-horvat My comment was a reply to what @adam said. I totally agree with you.

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

      @@andreibicu5592 Ah, sorry, it looked to me as the same commenter after a glance... My bad.

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

    What we gain from this aproach?
    Developer cannot assign wrong id accidentally. Which can easily be detected by unit tests... Not much benefit + more complexity...

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

      You said it all: "... easily detected by unit tests."
      Compilation is the static code analysis, whereas unit tests are dynamic analysis. SCA is always preferred for the simplest of all reasons: its conclusions are universally correct. Unit tests cannot achieve that.
      If unit tests were the answer, we would let go of compilers long ago.

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

    Why can't the MakeThisBookFamous method take a Book instead of a UUID and then we wouldn't even have that problem to solve? In your example, MakeThisBookFamous may be doing too much. I mean, it doesn't only make a book famous, but it also fetches a book from a database... which can fail... We're kinda mixing some internal business logic about how to make books famous with how books are persisted in our system... I don't understand... :(

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

      You have already answered your question. It is one of the methods that receives the ID and must load the object from the database. Every deserialization point, endpoint, controller and whatnot is precisely in that situation.
      Don't forget that in any model of a significant complexity you won't be loading all the objects until it becomes clear which object is the right subject. Most of the methods in an application would instead have access to numerous IDs, only sometimes deciding to materialize an ID into an object.

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

    Thanks for the video.
    The strongly typed Ids is a valid idea for protecting against mix of ids.
    But Ids in the domain entities is a design smell. They are persistence logic implementation details that have no relation to your domain. Use straight references instead. So "Book Book" instead of "Guid BookId".

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

      You cannot use the Book at the outer parts of the system. The UI and network will only communicate the ID.
      Also, it is not realistic that you will have numerous entities fully populated when only working with one of them. It is reasonable to move only the references to other entities around in operations that don't deal with them directly, not to load the entire objects in every operation.

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

      Ids are for the application layer. Not the domain model. The domain entities refer to each other by type.
      Regarding loading of the object graph: That is why you have "Lazy loading". That is one of the main benefits of NHibernate and EF.

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

      @@AndersBaumann That looks like a religious view on DDD and I cannot go much into depth commenting it. As an engineer, I know that I should not be querying a dozen times via lazy loading only to obtain a dozen other entities I don't plan to apply the current command to. That makes the models and all operations on them bloated with navigation properties nobody actually uses, all in the name of a very abstract goal of not seeing an ID, like ID is a separate concern.
      The original works by Eric Evans had defined IDs and explained their role. I agree with that explanation, it is correct. Eric has later even explained why entities cannot implement equality and yet I see everybody around start an entity by implementing its equality, even before defining its attributes. That is so wrong, as many other practices we see today are.

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

      Actually referencing entities that do not belong to each other is a code smell.
      Good luck to your code complexity and perfomance with lazy loading.
      Ids are fine.
      There are many many other "trade-offs" when it comes to DDD and micro-services communication, where certain domain classes use things, that typically belong to infrastructure.
      Even tho I dont like Zoran's code style as much, this video is a really really good advice that makes code easier to read/write.

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

      There is a foreign key reference so obviously they belong to each other.
      "Code complexity"? With ids you cannot unit test your domain model in isolation and you will move domain logic into the application layer. Seems like something that could quickly turn ugly if you are more than a few developers.

  • @allanhouston22
    @allanhouston22 16 днів тому

    This is called overengineering, ladies and gents

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

    00:42 A hash code, as per definition, does not qualify as identifier. Uniqueness is not guaranteed.

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

      You missed the point. The number generated by the runtime is unique, and even referred to as identity. The fact it is used in GetHashCode doesn't change that.

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

    This can lead to class proliferation problem

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

      I think it outweighs primitive obsession problem =P

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

      How?

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

      If you have a complex system with huge number of entities heaving a complex type to represent identity for each entity separately you will come up with huge number of small classes.
      But again, this is something related to big monolith projects. DDD on the other side tries to force you to split things in small bounded-contexts with relatively small number of entities. @@zoran-horvat

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

    Omg, write a lot of waste code for developers, who can’t read the name of method ((

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

      You can't imagine how short-sighted that view is.
      If the compiler is already doing the static code analysis, then why not let it tell the bugs back through the same process? You sound like you lost a bit of pride if you didn't do it yourself, do you?
      I remember an event when 70 people died and a certain programmer expressed that same attitude towards the guy who made the critical bug. Yet, the issue could have been prevented by the .NET runtime, if only it were in place.

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

      In my last large project we have spent a lot of time to remove this garbage and replace with generic logic in many places of code. It decrease memory consumption like 1,5 gb in every of 4 solutions and increase development speed for 3 times.

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

      @@1235663 You didn't watch the video, did you?
      What memory consumption are you talking about? It is identical before and after, to a byte.

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

      @@zoran-horvat you change int ids (or guid) to structs with values. this have it's consuption. We had for every objects(not only ids) legacy for every sub entities (some was structs, some classes). remove this approach help to decrease consuption (as i said before). on of reason was that we have very large caches (for calculation reasons). so some services decrease from 6 gb to 4.5. the main questions is that benefit of this approach is very arguable

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

      @@1235663 Stepping from int to GUID is driven by other reasons, as int is not sufficient to index the objects we use today, nor does it work in distributed systems, unless the price is paid in redesigning the system in other ways. Therefore, int or GUID is the false dilemma.
      After we get to using the long key, that is it. Whether it is primitive or a strong type doesn't matter. The amount of memory used in either case is identical, to a byte.
      If you tell me that you have reduced the amount of memory used by 25% by only shortening the ID, then where are the data besides the ID? I mean, there must be a reference to each object as well, that is another 1.5GB. What are you telling me, that one half of your database are the keys? That is either not true or your service is one in a thousand and whatever conclusions you make from it are irrelevant in the general case. Your numbers just don't add up.

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

    So you're writing more code because you didn't check what you were doing or name the parameter something useful like bookId? No wonder you got it wrong

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

      Three lines you count as more code but checking what you are doing is for free.
      You know better than that.

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

      @@zoran-horvat per Id per Entity. And it's a lot more than 3 lines. I know because I managed to persuade the team to not overengineer solutions because of primitive envy.

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

      @@TheDiggidee Not per ID per entity but one per entity, located in the same file with the entity, for clarity. That does not qualify as overengineering. The solution is adapted to static code analysis like so many other parts of the model and even language syntax.

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

      @zoran-horvat yes it is per I'd per entity. Put the damn code wherever you want. Doesn't make it cleaner or solve the problem that you put the wrong id in. Just makes it clearer that you did. This problem could be solved by writing a unit test and double checking your work. The entire problem is down to bad naming conventions and not checking. Therefore it's overengineering because you're doing more work when you could just check.