Learn To Love DDD-Style Strongly Typed IDs

แชร์
ฝัง

ความคิดเห็น • 160

  • @heintaljaard8210
    @heintaljaard8210 5 หลายเดือนก่อน +13

    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 5 หลายเดือนก่อน +24

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

  • @MacHmura
    @MacHmura 5 หลายเดือนก่อน +28

    Really enjoying your new style of videos, thanks!

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

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

    • @MC_DarkMaster
      @MC_DarkMaster 5 หลายเดือนก่อน +1

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

    • @MacHmura
      @MacHmura 5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      @@MC_DarkMaster haha, cant be that ;)

  • @alexhall840
    @alexhall840 5 หลายเดือนก่อน +7

    Amazing story telling and practical programming techniques that work

  • @nicklaspillay7923
    @nicklaspillay7923 5 หลายเดือนก่อน +3

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

  • @leos-clockworks335
    @leos-clockworks335 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +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.

  • @dcuccia
    @dcuccia 5 หลายเดือนก่อน +1

    Nice format, and great lesson. Thank you!

  • @davidharper8132
    @davidharper8132 5 หลายเดือนก่อน +1

    Wonderful explanation, thank you kindly!

  • @luc9volts
    @luc9volts 5 หลายเดือนก่อน +1

    Really clever solution 👍🏻

  • @preservedmoose
    @preservedmoose 5 หลายเดือนก่อน +2

    This is excellent!

  • @karlvwg2074
    @karlvwg2074 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน

      A wrapper type essentially behaves like a derived type.

  • @IDrDoh
    @IDrDoh 5 หลายเดือนก่อน +1

    Love it !

  • @kennyhendricks4293
    @kennyhendricks4293 5 หลายเดือนก่อน +1

    Top stuff, makes sense...

  • @hectorkrionas-lamprou4922
    @hectorkrionas-lamprou4922 5 หลายเดือนก่อน +1

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

  • @timur2887
    @timur2887 3 หลายเดือนก่อน

    Sounds like a good-old D&D tale)

  • @mortensnetoftwernberg8784
    @mortensnetoftwernberg8784 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +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.

  • @michaelwong1908
    @michaelwong1908 5 หลายเดือนก่อน

    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?

  • @xxfrikoxx
    @xxfrikoxx 4 หลายเดือนก่อน

    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  4 หลายเดือนก่อน

      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.

  • @fifty-plus
    @fifty-plus 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +1

      Excellent. Many thanks.

  • @MetronSM
    @MetronSM 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +1

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

  • @mihaiga
    @mihaiga 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      ​@@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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      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?

  • @robertodalmonte504
    @robertodalmonte504 5 หลายเดือนก่อน +7

    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 5 หลายเดือนก่อน

      What would T be? Any class?

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

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

  • @magicsmoke0
    @magicsmoke0 หลายเดือนก่อน

    Instead of entity framework, I’d like to see how you would make this work with cosmos db client library directly.

  • @BlackJar72
    @BlackJar72 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

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

  • @pinguincoder
    @pinguincoder 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      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  5 หลายเดือนก่อน +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.

  • @zionen01
    @zionen01 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

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

  • @AlBex-dc6yq
    @AlBex-dc6yq 4 หลายเดือนก่อน

    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  4 หลายเดือนก่อน +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 4 หลายเดือนก่อน +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  4 หลายเดือนก่อน

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

  • @theyuribanker
    @theyuribanker 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      Possibly yes.

  • @user-zq4ch2hp5u
    @user-zq4ch2hp5u 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน

      MakeFamous(Book book)

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +2

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

    • @RoiTrigerman
      @RoiTrigerman หลายเดือนก่อน

      ​@@djdaveydave123consider a common situation where you get the id of a book from a client. you will then have to first fetch the entire book from the database only to pass it to the makeFamouse method?

  • @andyhb1970
    @andyhb1970 หลายเดือนก่อน

    I like the idea but how on earth do you handle nullable foreign key types in your entity framework configuration class?

    • @zoran-horvat
      @zoran-horvat  หลายเดือนก่อน

      EF Core supports that out of the box. I did it in one of the videos, I think.

    • @andyhb1970
      @andyhb1970 หลายเดือนก่อน

      @@zoran-horvat hmmm I can't get my code to compile, I'll see if I can track the video down

    • @andyhb1970
      @andyhb1970 หลายเดือนก่อน

      Oh crikey, simple as that... countryId!.Value.Value I missed the last .Value LOL!

    • @zoran-horvat
      @zoran-horvat  หลายเดือนก่อน

      @@andyhb1970 Yes, I remember it was easy.

  • @MC_DarkMaster
    @MC_DarkMaster 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      Add a custom JsonCoverter for this Type.

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      @@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 5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +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

  • @mdev3987
    @mdev3987 5 หลายเดือนก่อน

    Does it work with entity framework and mssql?

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +1

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

    • @mdev3987
      @mdev3987 5 หลายเดือนก่อน

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

  • @justblue4864
    @justblue4864 5 หลายเดือนก่อน +5

    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 5 หลายเดือนก่อน +1

      why extrapolating? 😊

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +3

      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.

  • @thygrrr
    @thygrrr หลายเดือนก่อน

    / *reads Bertrand Meyer, the Book* /

  • @gbsyi_
    @gbsyi_ 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

      Both APIs should contain the types they are using.

  • @MrNickP
    @MrNickP 4 หลายเดือนก่อน

    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  4 หลายเดือนก่อน +2

      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.

  • @noosetime9423
    @noosetime9423 3 หลายเดือนก่อน

    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  3 หลายเดือนก่อน

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

    • @noosetime9423
      @noosetime9423 3 หลายเดือนก่อน

      @@zoran-horvat oh, ok, thats interesting

  • @lollol35
    @lollol35 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน

      @@zoran-horvatAwesome! Looking forward to it.

  • @auronedgevicks7739
    @auronedgevicks7739 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน

      @@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  5 หลายเดือนก่อน

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

    • @auronedgevicks7739
      @auronedgevicks7739 5 หลายเดือนก่อน

      @@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  5 หลายเดือนก่อน

      @@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 5 หลายเดือนก่อน

    @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 5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน

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

  • @Kubkochan
    @Kubkochan 5 หลายเดือนก่อน

    how to push this concept down to api?

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

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

  • @OldanFatte
    @OldanFatte 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

      Wrong.

    • @OldanFatte
      @OldanFatte 5 หลายเดือนก่อน

      ​@@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?

    • @OldanFatte
      @OldanFatte 5 หลายเดือนก่อน

      @@zoran-horvat
      TH-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?

  • @alexeybeloushko7240
    @alexeybeloushko7240 4 หลายเดือนก่อน

    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  4 หลายเดือนก่อน

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

    • @alexeybeloushko7240
      @alexeybeloushko7240 4 หลายเดือนก่อน

      @@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  4 หลายเดือนก่อน

      @@alexeybeloushko7240 Are you more arrogant or wrong?

    • @alexeybeloushko7240
      @alexeybeloushko7240 4 หลายเดือนก่อน

      @@zoran-horvat about what? be more specific

  • @adambickford8720
    @adambickford8720 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      @@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 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

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

  • @publishyours
    @publishyours 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน

      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.

  • @krccmsitp2884
    @krccmsitp2884 5 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      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.

  • @metehanmutlu9187
    @metehanmutlu9187 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน

      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.

  • @allanhouston22
    @allanhouston22 2 หลายเดือนก่อน

    This is called overengineering, ladies and gents

    • @zoran-horvat
      @zoran-horvat  2 หลายเดือนก่อน

      But of course.

  • @AndersBaumann
    @AndersBaumann 5 หลายเดือนก่อน

    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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      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  5 หลายเดือนก่อน

      @@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 5 หลายเดือนก่อน +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 5 หลายเดือนก่อน

      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.

  • @dalizdr
    @dalizdr 5 หลายเดือนก่อน +1

    This can lead to class proliferation problem

    • @nicknelson1975
      @nicknelson1975 5 หลายเดือนก่อน +2

      I think it outweighs primitive obsession problem =P

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน

      How?

    • @dalizdr
      @dalizdr 5 หลายเดือนก่อน

      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 5 หลายเดือนก่อน +1

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

    • @zoran-horvat
      @zoran-horvat  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +3

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

    • @TheDiggidee
      @TheDiggidee 5 หลายเดือนก่อน +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  5 หลายเดือนก่อน +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 5 หลายเดือนก่อน +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.