Learn To Love DDD-Style Strongly Typed IDs

แชร์
ฝัง
  • เผยแพร่เมื่อ 27 พ.ย. 2024

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

  • @heintaljaard8210
    @heintaljaard8210 9 หลายเดือนก่อน +20

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

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

  • @MacHmura
    @MacHmura 10 หลายเดือนก่อน +31

    Really enjoying your new style of videos, thanks!

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

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

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

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

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

      @@MC_DarkMaster haha, cant be that ;)

  • @alexhall840
    @alexhall840 10 หลายเดือนก่อน +9

    Amazing story telling and practical programming techniques that work

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

    mr. horvat your content is incredibly inspiring! i often rewatch your videos experienceing some kind of sudden ephiphany of education, enticing me to reiterate on my past solutions!!! so much that i recently became a patreon, thank you zoran

  • @leos-clockworks335
    @leos-clockworks335 10 หลายเดือนก่อน +4

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

      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.

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

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

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

      A wrapper type essentially behaves like a derived type.

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

    Nice format, and great lesson. Thank you!

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

    Wonderful explanation, thank you kindly!

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

    Love this idea but still can't find a good way to handle it in an application due to JSON, EF Core, API endpoints, etc. all needing converters, serialization methods, parsing, etc.
    Just a UserId wrapping a Guid, for example:
    - UserId must implement IParsable so it can be used in API endpoints.
    - UserId needs a UserIdJsonConverter so it isn't drawn as id: { value: "guid" } in JSON responses.
    - UserId needs a UserIdValueConverter so that EF Core can treat it like a Guid rather than an owned entity.
    And none of this works with inheritance, so you must do this for every ID in the domain, even if you use a ValueObject or StrongId base record.

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

      @@tuckertcs Actually, it is not the duty of the domain model to serialize/deserialize itself, just like it is not its responsibility to persist itself.

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

      @@zoran-horvat This is true, however it's generally expected, so whether you handle it in the domain layer or the persistence/presentation layers, it's still something you generally need to do.
      Additionally, many objects do handle this logic within themselves. Guid implements IParsable, so why shouldn't UserId?

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

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

      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.

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

      What would T be? Any class?

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

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

  • @pinguincoder
    @pinguincoder 9 หลายเดือนก่อน +1

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

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

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

    This is excellent!

  • @rmcgraw7943
    @rmcgraw7943 2 วันที่ผ่านมา

    Also, don’t create IMPLICIT operator overloads on Strongly Typed ID classes. If you do, have fun debugging. U can use Explicit, but I wouldn’t recommend implilcit ones.

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

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

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

    Sounds like a good-old D&D tale)

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

    Really clever solution 👍🏻

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

    Hello Zoran, thank you for this video. I wanted to apply this idea to a Guid based ID which would be serializable.
    Do you see any pitfalls to an approach which guards against empty GUIDs by always setting them to Guid.NewGuid if empty?
    I'm wondering whether this can lead to bugs in a context of JSON serialization.
    My reasoning for not allowing Guid.Empty is that the IDs are then not unique anymore and thus two objects can clash.

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

      @@juliusdeblaaij2534 If you wrapped the GUID into a record, that record could ensure that the value is never empty.

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

    Top stuff, makes sense...

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

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

      MakeFamous(Book book)

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

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

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

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

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

    How do I host a composite list of 2 Types of IDs? So the List can contain BookID and AuthorID

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

    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?

  • @Cool-Game-Dev
    @Cool-Game-Dev 2 หลายเดือนก่อน

    I use C# for game dev and don't find this applicable to my current project. That said, I am 100% with you on this, besides the already mentioned JSON annoyance.

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

      @@Cool-Game-Dev This is definitely not the topic that applies in game modeling.

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

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

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

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

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

      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.

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

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

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

  • @KaineVarley
    @KaineVarley 23 วันที่ผ่านมา

    Suppose that you needed access to the underlying Guid, what's the best way to cast it back into a Guid? Attempting to `(Guid)stronglyTypedId` results in 'CS0030: Cannot convert type 'stronglyTypedId' into 'System.Guid'.' Converting it to a `string` then back to a Guid feels wrong.

    • @zoran-horvat
      @zoran-horvat  23 วันที่ผ่านมา +1

      Conversion back and forth happens at the edges of the model. That is at the endpoint or UI, where it is transformed into a primitive type, often a string, or at the persistence layer where it is transformed into a type supported by the storage.
      For example, when using EF Core, you would define a value conversion that applies to a strongly typed ID.

    • @KaineVarley
      @KaineVarley 23 วันที่ผ่านมา +1

      Thx @@zoran-horvat, that's precisely where I'm having to do the conversion. It's a D365 integration piece, and I need to convert my model into an Entity, which takes a Guid as it's Id. So, right at the margin, or endpoint.

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

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

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

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

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

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

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

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

      @@andyhb1970 Yes, I remember it was easy.

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

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

      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?

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

      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.

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

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

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

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

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

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

      @@zoran-horvat oh, ok, thats interesting

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

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

      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.

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

    Love it !

  • @MlPhone-d2h
    @MlPhone-d2h 10 หลายเดือนก่อน

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

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

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

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

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

      Possibly yes.

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

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

      Wrong.

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

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

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

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

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

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

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

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

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

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

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

      Both APIs should contain the types they are using.

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

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

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

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

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

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

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

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

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

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

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

      Excellent. Many thanks.

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

      why extrapolating? 😊

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

      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.

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

      As with everything, philosophy matters. If I'm going to cut a bit of wood a saw will do, it I'm building a cabinet, a sophisticated set of precise tools is required. Such tools use the same primative ideas. However, their design trades versatility for precision. I think it's important to realise he does this for the domain model (the most important code), such an approach to all the accidental complexity within an application would lead to a frustrated customer.

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

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

      Add a custom JsonCoverter for this Type.

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

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

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

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

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

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

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

    What are your thoughts on using “real-world” IDs.
    e.g. ISBN* for the ID of the book entity/table.
    Employee number for Users (or UserName).
    Invoice Number for invoices, etc.
    I don’t know enough about these numbers to know if this would work, it is really just an example.

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

      @@JonathanPeel Natural keys are good in communication, URLs, etc. but they are not ideal as identifiers. Most notably, they might change for an existing entity for various reasons.

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

    / *reads Bertrand Meyer, the Book* /

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

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

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

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

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

      @@alexeybeloushko7240 Are you more arrogant or wrong?

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

      @@zoran-horvat about what? be more specific

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

    Does it work with entity framework and mssql?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      @@zoran-horvatAwesome! Looking forward to it.

  • @OscarAgreda
    @OscarAgreda 3 หลายเดือนก่อน +1

    This approach of using strongly typed identities elevates identity management from a simple necessity to a core practice in software development, promoting higher code quality and reliability across various domains.

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

      gpt

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

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

      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.

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

    how to push this concept down to api?

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

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

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

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

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

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

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

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

      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.

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

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

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

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

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

    This can lead to class proliferation problem

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

      I think it outweighs primitive obsession problem =P

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

      How?

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

      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

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

    This is called overengineering, ladies and gents

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

      But of course.

  • @1235663
    @1235663 10 หลายเดือนก่อน +1

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

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

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

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