How to Use Value Objects to Solve Primitive Obsession

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

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

  • @MilanJovanovicTech
    @MilanJovanovicTech  2 ปีที่แล้ว +5

    Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
    Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt

  • @eddypartey1075
    @eddypartey1075 9 หลายเดือนก่อน +4

    What a pleasure to have such a brilliant developer and also teacher for .NET community!

  • @fieryscorpion
    @fieryscorpion 2 ปีที่แล้ว +17

    You're very talented and bound to be a great teacher. Loved the video and subbed instantly!

  • @YuriWithowsky
    @YuriWithowsky 2 ปีที่แล้ว +4

    Excellent Milan! Please never stop! Your videos are very good

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +1

      Thank you Yuri. As long as there are awesome people like you supporting me, I will have a reason to create more videos :)

  • @EmptyGlass99
    @EmptyGlass99 ปีที่แล้ว +2

    Your explanations are very comprehensive and logical.

  • @pureevil379
    @pureevil379 ปีที่แล้ว +5

    Great video on this topic. I agree the complexity is increased and you have to weight up what you gain. Looking forward to the input validation video (going through them all).
    What I've done before for validation is a isValid method that lives within the member entity. If not valid it returned null and I checked for that.
    After watching your other video I'd use a result instead :)
    Thanks as always!

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      Check out FluentResult library if you don't want to write your own

  • @kinggrizzly13
    @kinggrizzly13 2 ปีที่แล้ว +1

    Great example. When I built my ValueObjects I also used added domain service functionality using an interface to be able to pull my constraints from a database, e.g. MaxLength, MinLength, IsRequired etc.

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Oh, so you were fetching constraints from the database? Why wouldn't you force you database constraints from the code?

    • @kinggrizzly13
      @kinggrizzly13 2 ปีที่แล้ว +2

      @@MilanJovanovicTech it was a business requirement. They wanted the ability to change those without us compiling the code, even though the chances of those values changing were small… either way it was a fun exercise to implement.
      Basically we created tables to store value object constraints and pulled from them. Hopefully that is a better explanation of the requirement.

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

    Good thing pointing out the negative aspects in the end. 👍 Everything are tradeoffs and not all patterns fit everyone and sometimes not even most ones.

  • @joshem32
    @joshem32 2 ปีที่แล้ว +2

    Excellent Milan, I really enjoy your videos, it's always nice to know about different views or different possibilities on how to structure your solution!!

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Thank you very much, Jose. Did you work with Value Objects before?

    • @joshem32
      @joshem32 2 ปีที่แล้ว

      @@MilanJovanovicTech not really, this is all new to me, maybe because I'm afraid of adding extra complexity to my projects, but at the end of the day it totally makes sense.

  • @EldonElledge
    @EldonElledge ปีที่แล้ว +2

    Great video. You are ver very good at explaining complex subjects in an easy to understand way. I also like how you explain the trade-offs for the different approaches.

  • @adisilagy
    @adisilagy ปีที่แล้ว +2

    Great video Millan!
    I just want to point out that if we wish to work with Dapper alongside EF, we should create SqlMapper for each of the value objects so Dapper will know how to map these properties

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Or maybe have a separate model for Dapper?

    • @adisilagy
      @adisilagy ปีที่แล้ว +1

      @@MilanJovanovicTech What is the benefit of having a seperate model?
      As I View it, by adding a mapper for each value object you can you simply use the existing model entities.
      Then I use EF for commands and Dapper for queries with extension method on the DbContext

  • @matesebestyen3142
    @matesebestyen3142 ปีที่แล้ว +1

    Great video Milan, I really like your work, especially because I've encountered many similar issues at my workplace - working at a startup as a freshly graduated Software Engineer.
    I actually prefer throwing exceptions when domain rules are broken - you save the added complexity on the caller side, and you have to write less code - of course, the tradeoff is losing the control of handling errors neatly (hate try-catch).
    Keep up the good work, you are making better software engineers for tomorrow!

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Thank you very much! I'm humbled :)
      Newtonsoft.Json can get by with a private constructor I believe?

    • @matesebestyen3142
      @matesebestyen3142 ปีที่แล้ว +1

      Looked it up and you are right! :)

  • @implicat
    @implicat ปีที่แล้ว +2

    It would be great to add a link to the video you mentioned during your speech. For example "If you didn't watch my video about Result,..." and video appears in right top corner.
    It will increase the amount of views, I'm sure :)

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Still learning about that stuff. I have to purposefully remember to say that while recording a video. My newer videos are better in that regard.

  • @batressc
    @batressc 13 วันที่ผ่านมา

    Just a little comment about the Value Objects and my own experience: Definitively ValueObjects it's the better approach when you developing an application using the DDD patterns, but if you are using Entity Framework you must be careful about how that Value Objects will be translated to a T-SQL or other SQL language (postgres, mysql, etc). By default, Entity Framework don't have a idea how can do that, you can apply some tricks (casting and creating implicit operators on the Value Object) but this is ok in scenarios were you have the full control how create the Linq sentences; if you use a control suite like a DevExpress or Telerik, on backstage the controls don't know how to handle that kind of objects and you can find some error on controls like DataGrid were cannot translate the linq sentences.

    • @MilanJovanovicTech
      @MilanJovanovicTech  12 วันที่ผ่านมา

      We can pass primitive types to the external components, right?

  • @joga_bonito_aro
    @joga_bonito_aro ปีที่แล้ว +2

    Wouldn't a record type be a sufficient replacement for a ValueObject? You get type safety, immutability and equality out of the box and way shorter to type.
    Am I missing something?
    I see that it's already answered way down below

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      I've recently started using records, and honestly find them just fine for ValueObjects :)

  • @TheMostafa5000
    @TheMostafa5000 ปีที่แล้ว +1

    Thanks Milan for the video and I really got excited when I saw World Of Warcraft behind you 🤩

  • @yazanshakhshir3049
    @yazanshakhshir3049 ปีที่แล้ว +1

    Thanks for the great video as well Milan.
    I am a big big fan of your content and I really enjoy learning new stuff from you.
    I have a dilemma, now I know the basic approach which is using the primitive types, and currently, I just learned a new approach which is using the value objects.
    In the end, you recommended not to use this always and we have to weigh the pros and cons, which is really difficult tbh, that is, it is hard to tell when I should use this over that!
    so, I am telling you :D, it would be really great and helpful if you create a new video that illustrates and gives a real use case that when to choose either of the two approaches and how to weigh the pros and the cons.
    Thanks a lot again,
    keep up the great work.

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

    Great video as always Milan. I advise devs to default to primitive types, with attribute constrains or using specific setter methods (as opposed to public property setters). This lead to better productivity. I believe that level of value objects should be used in niche cases

  • @isobato
    @isobato 2 ปีที่แล้ว +2

    I love your videos Milan! I did not catch if we made the ctor private. We could bypass the Create method and bypass the limitations we established, right? Love your channel, even thought I've subscribed I'm still manually checking for new videos on daily basis 🤣

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +1

      Thanks a lot Bratislave! 😁 Yes, I believe I made the ctor private, precisely for that reason.
      The release schedule is Tuesday-Friday usually.

    • @peymanebrahimi7756
      @peymanebrahimi7756 2 ปีที่แล้ว +1

      Same here. I check manually 2 or 3 times a day for new video.

  • @100kshooter5
    @100kshooter5 2 ปีที่แล้ว +3

    Awesome video, l was actually waiting for this notification, what do you think about having validation being the responsibility of something like FluentValidation in a DDD project, on the handlers?

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +8

      I try to split my validation into two parts:
      1. Input validation - which I solve in the Application layer using FluentValidation (coming out in a future video)
      2. Domain validation - which I usually implement by enforcing constraints using DDD principles.

    • @herve952
      @herve952 2 ปีที่แล้ว

      @@MilanJovanovicTech What's the point of having 2 validations in 2 different places, doesn't that introduce code duplication ? Moreover the application layer doesn't actually use those inputs so why should it care about it, in my opinion validation of its state belongs to the domain, doesn't it ?

    • @stjepankarin
      @stjepankarin 2 ปีที่แล้ว +4

      An example to the rescue, I hope… 😀
      Let’s say you have an API-endpoint expecting to get an e-mail address for whatever reason. Now, an input validation would ensure that incoming string is actually a valid e-mail address, like format-wise, ensuring the string is in a form of whatever current RFC standard rules. If format is wrong we would respond with status code 400. If format is ok, we would move on (hopefully creating an Email value object or something) to the business side.
      Domain rule, on the other hand, might say that only specific e-mail addresses are allowed, like from specific domains or from some hard-codes list, or maybe that only unique ones are allowed, or whatever. So you have an e-mail address, but not the “right” one. 😃
      Those rules are part of our business and needs to be handled differently, hence domain validation. If an e-mail address is invalid at this point you might for ex. throw a domain exception which would be handled slightly differently, responding with status code 403/409/whatever. If it is good one, we proceed and complete our case.
      As you see, there is different meaning/reason for validation, so keeping this separate will ensure we can more easily evolve the overall solution.

  • @dimitricharles9784
    @dimitricharles9784 2 ปีที่แล้ว +1

    Very nice video, very well explained. I will use this technique to create new instances

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Awesome, I'm glad you liked it. Just don't overdo it! 😁

  • @rezvlt9285
    @rezvlt9285 ปีที่แล้ว +1

    I wish my code was as clean as yours. That's why I'm watching ;)

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      You can do it! Just write it badly 100x times and then figure out how to write it better

  • @CousinAnthony
    @CousinAnthony 2 ปีที่แล้ว +1

    Without starting a debate on Guid vs int primary keys,,
    what about making the Entity class Entity where you would have the option to specify the Primary key type?
    I attempted this on my own, but I am getting a compile error in the Equals methods
    return other.Id == Id;
    return entity.Id == Id;
    Both these line generate the following error:
    Operator 'operator' cannot be applied to operands of type 'T' and 'T'

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      That's a good option if you want to do strongly typed Ids. But I felt it would be too much for this video.
      I may make a separate video on that topic.
      What you can do is use create a marker interface for the EntityId, and implement it as a record.

  • @Rev1211b
    @Rev1211b ปีที่แล้ว

    I'm really enjoying this stuff. Nice work!

  • @brechtlaitem
    @brechtlaitem 2 ปีที่แล้ว +8

    Why not using a record instead of a class to achieve immutability?

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +2

      Great question, Brecht! I don't recommend using records because it is very easy to break constraints with records.
      Records allow modifying the instance using the *with expression*, which calls the copy constructors.
      True, they solve immutability. But they fall short of the other important attributes.

    • @allannielsen4752
      @allannielsen4752 2 ปีที่แล้ว +3

      @@MilanJovanovicTech in my understanding, 'with' still calls the constructor and creates a new instance. It does not alter the original instance. This scenario is exactly what records are good at. I really like your static Create returning the result though.
      try the following:
      var bob = new FirstName("bob");
      var jane = bob with { Value = "jane", Mandatory = false };
      Console.WriteLine(bob);
      Console.WriteLine(jane);
      public record FirstName(string Value) : Names;
      public abstract record Names(int Length = 50, bool Mandatory = true);
      OUTPUT:
      FirstName { Length = 50, Mandatory = True, Value = bob }
      FirstName { Length = 50, Mandatory = False, Value = jane }

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +1

      @@allannielsen4752 Now try enforcing a maximum f 50 characters in the first name.
      You have to put that logic inside of the setter.
      Possibly throw an exception?
      And then you have to do that for each property that can be set.

    • @connorradeloff5597
      @connorradeloff5597 2 ปีที่แล้ว +1

      @@MilanJovanovicTech If you needed to enforce validation you could expose an function to set the value which performs the validation internally and returns some sort of Validation/Option result which contains the new instance of the mutated object, otherwise a Failure/Nothing. This way the caller knows if the operation succeeded or not, and we maintain immutability. If the setter on the property is private, you cannot use the with operator to set the value.

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

      Great article))) Despite the fact that it's great approach of immutability. But imagine when your entity is getting bigger for instance could have 20 properties. In that case I should create ValueObject for of each of them. How to support this kind of code?

  • @Embriiii
    @Embriiii ปีที่แล้ว +1

    What about a "protected string Value" to enforce our system to use only instances of value objects and preventing the use of the primitive Values? When it's not necessary to expose the Value of course

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

    I get your point, but what if we have many properties that we need as value objects? Does it mean we go through route?

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

      Depends on how much you want to complicate your domain, and if there's value in that

  • @Mefhisto1
    @Mefhisto1 2 ปีที่แล้ว +2

    Good stuff.

  • @eduugr
    @eduugr ปีที่แล้ว

    Good stuff... old wolf getting up to date on software architecture here, well exemplified sir! Thanks

  • @majormartintibor
    @majormartintibor 2 ปีที่แล้ว +1

    Thanks for this. I will refactor my code based on this.

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +1

      Important: Ask yourself if you really do need this complexity? If yes, go for it! 😁

    • @majormartintibor
      @majormartintibor 2 ปีที่แล้ว +1

      @@MilanJovanovicTech yes, yes, I know what you mean. I am a fan of keeping things simple for as long as it works fine. I will not blindly change all primitives, only those where it does make sense.

  • @zutafe
    @zutafe ปีที่แล้ว +1

    It would be great if you can cover a niche topic on Strongly-typed Ids (guard types) that EF Core 7 has a built-in support (but still need to write more code).

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      What built-in support? I didn't hear 🤔

    • @zutafe
      @zutafe ปีที่แล้ว

      @@MilanJovanovicTech I can 't post link here, you can check "Value generation for DDD guarded types" in EF Core documentation for details.

  • @RoaringOrange
    @RoaringOrange 2 ปีที่แล้ว +2

    It is an obsession indeed! Chasing phantom issues...

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Why do you think this is the case?

    • @RoaringOrange
      @RoaringOrange 2 ปีที่แล้ว

      @@MilanJovanovicTech it complicates the code, rapes the memory with all the extra objects allocated/deallocates, messes with the model. Now you need to not only convert from/to user input to your obsession models, but also to/from database models. You will still have to expose normal types in api if you planning to have any, unless you want the consumers to be forced into this insanity. The idea is really cute though. And I use the word phantom, cos it still doesn't prevent the user from swapping first and last name. Your best bet - write a test and validate the output. That way you know that inside your code there are no issues like you described and you don't have to obsess over anything.

  • @xBalaDeCanhaox
    @xBalaDeCanhaox 2 ปีที่แล้ว +1

    Great explanation of ValueObjects although it's really complex to implement. And this is just the FirstName, I'm wondering a bunch of fields to validate and create factory methods. lol

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      It gets cumbersome, but when working in complex systems it's worth it to be honest.

  • @lukaskuchta1010
    @lukaskuchta1010 ปีที่แล้ว +1

    Good job man! One question here. Why the ValueObject implementation is class and not struct? Or why is there IEquitable for class ? I seen somewhare that main beneffit of this is for struct types and no boxing - like with standard way of Equals. Thanks.

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      Structs don't have inheritance, so that's one thing. Records could be a really nice alternative.

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

      @@MilanJovanovicTech But isn't mapping records to Ef core a problem by itself ?

  • @Philippe42460
    @Philippe42460 ปีที่แล้ว +1

    Hi Milan and thank you for this interesting video.
    Now I have a question: can the value object be replaced by a mere record? I think it could since a record already implements Iequatable. Thus all it need is making the validating logic you described... or maybe now there could be a more generic way to do it?...

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

      Yes! And I've been using records in recent times for Value Objects

  • @vivekkaushik9508
    @vivekkaushik9508 ปีที่แล้ว +1

    Is there a reason why we should prefer Value Objects over Structs?

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      Structs always have a default ctor which breaks encapsulation, with a class I can impose stricter constraints

  • @Skillamu
    @Skillamu ปีที่แล้ว +1

    Great video!

  • @mahmoudalballah3387
    @mahmoudalballah3387 ปีที่แล้ว +1

    Awesome but I think FirstName is not a good choice to explain value object with I think Address would be great example.

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      You may have a good point there, but keep in mind I was still a fresh content creator at the time 😅

  • @evilTano
    @evilTano 2 ปีที่แล้ว +1

    Hi Milan, great video! But I've some questions:
    The Entity should ALWAYS accept a valid FirstName? So the check should be done outside of the Entity? Could this provide a lot of duplicate check in every CommandHandler? For example if I have two CommandHandler one for the Creation and one for the Update?
    Thanks.

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Indeed, duplication is a common theme here. In general, I don't mind it. But if you think it's problematic, you can try to think of a way to reduce duplication

  • @ayalamac
    @ayalamac ปีที่แล้ว +1

    Hey Milan, what do you think of ValueOf? and, How would you use this with FluentValidation? meaning, I'd like to have value objects for common properties among my entities and I'd have specific IValidators ... ?

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      I haven't worked with it, so I'm not very familiar to be honest

  • @10199able
    @10199able 2 ปีที่แล้ว +2

    something something Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F#

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      I haven't read the book yet. Do you recommend it?

    • @10199able
      @10199able 2 ปีที่แล้ว +1

      @@MilanJovanovicTech There is literally string50 type in it!

  • @wilfriedkinda
    @wilfriedkinda ปีที่แล้ว +1

    Hi Milan, thank you for this instructive video as always,
    I was just wondering if we could replace Sealed FirstName class with a reord type!?

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Yes, I've actually been exploring that recently and I think records are fine for value objects

  • @MrJonnis13
    @MrJonnis13 ปีที่แล้ว +1

    Hi Milan and thank you for the effort.
    If you don't throw here a validation exception for firstName, how are you going to signal the error in the presentation layer (controllers) to the UI/FE side, so that the end user knows that the member couldn't be created if we just log the error ?
    Maybe I am missing what the "Unit.Value" contains

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      At this point in the series, there still wasn't any error propagation to the API. What I did in a later video is return the Result to the Controller level, and convert the error into a ProblemDetails

    • @MrJonnis13
      @MrJonnis13 ปีที่แล้ว +1

      @@MilanJovanovicTech I see. Thank you

  • @jon-slem
    @jon-slem 2 หลายเดือนก่อน

    This maybe old, but would a record does the same thing relating to structure integrity and immutable?

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

      Conceptually yes. But records have some downsides: www.milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals

  • @JonathanPeel
    @JonathanPeel 2 ปีที่แล้ว +1

    I have heard of systems being described as "Stringly Typed"

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      That sounds fantastic! 😂 Quite eye-opening, honestly

  • @syedib
    @syedib 2 ปีที่แล้ว +1

    Why dont we create Name ValueObject and encapsulate firstname and lastname. do you think still we have chance to pass Name.Lastname instead of Name.Firstname ?

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      That's also a good option, assuming we want the same rules.
      You could also implement Name with all the business logic, and then just create FirstName and LastName which inherit from Namw for the strong typing.

  • @maxpuissant2
    @maxpuissant2 2 ปีที่แล้ว +1

    I don't understand how are you going to rehydrate this value object, if constructor is private whitout setter or init, ef core is not going to be able to spawn this object?
    Also, would you use it for the Guid? If so ef core would need to be configured accordingly I guess.

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      I configure a value converter behind the scenes. And EF can call private constructors without issues!
      I didn't understand your second question about Guid. What did you mean?

    • @maxpuissant2
      @maxpuissant2 2 ปีที่แล้ว

      @@MilanJovanovicTech do you use a value object to encapsulate the Guid type? If yes then is there configuration of EF needed? Thank you

  • @aah134-K
    @aah134-K ปีที่แล้ว +1

    I love to use value objects but the main issue is when mapped to database, usually they dont have ids which make them uneasy to use with entityframework

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      That is the point precisely, they are not supposed to have Ids! You want to map them to columns inside of the principal table.

    • @ppenxhchqlz3113
      @ppenxhchqlz3113 ปีที่แล้ว

      They are properties of an entity basically, why would you want them to have ids?

    • @aah134-K
      @aah134-K ปีที่แล้ว

      @@ppenxhchqlz3113 i dont but how to know where they fit in the system without ids?

  • @stunna4498
    @stunna4498 ปีที่แล้ว +1

    Do you normally use Guid for primary keys by default?

  • @prathameshshende4
    @prathameshshende4 ปีที่แล้ว +1

    after all this error exception setting, do also need to set this in client side project like blazor? So it will be the double work, right? For this setting exceptions setting can we use FluentValidation instead of valueobject for each class and property?

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว +1

      I haven't worked with Blazor so I can't really say

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

    Hi Milan, often Currency is considered a value object, but if it allows crud operations (add new currency, update currency description), Is it still a value object? Aggregates, like BankAccount, could reference Currency object or just id (CurrencyId)?

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

      I think you're confusing data management with behavior

  • @TimurBabyuk
    @TimurBabyuk 2 ปีที่แล้ว +1

    For this purpose, OOP style is over complicated. For example, in F# with structural equality and discriminated out of the box all domain with these value objects and validation would fit into a dozen lines of code. By using bind, no need to throw exception on every data field validation. I'd recommend checking out Scott Wlaschin's "Domain modelling made functional" book and his numerous talks on TH-cam, including railway oriented programming

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +1

      Well F# is very different from C# in that regard, no wonder it's much less code.
      I really like ROP, and I intend to show it in some of the future videos!

  • @jesusantoniomartinezhernan2791
    @jesusantoniomartinezhernan2791 2 ปีที่แล้ว +1

    Hi Milan. I used to use a abstract class for value objects, but, i think that record type provide enough usefulness for value objects. What do you think about it???

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      The problem with records is you can easily break invariants using the *with* expression.
      You can add guard clauses inside of the property setters, but that completely misses the point to be honest.

    • @jesusantoniomartinezhernan2791
      @jesusantoniomartinezhernan2791 2 ปีที่แล้ว +1

      @@MilanJovanovicTech i get it. Well, i think that with expression dont change a record instance, but it build a new instance with this change inside, so, a long short story, can preserve invariants. Well, for things that comparaision and equality, it's works.

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      ​@@jesusantoniomartinezhernan2791 Yes, but you aren't preventing breaking the invariants of the new instance. Are you?

  • @microtech2448
    @microtech2448 ปีที่แล้ว +1

    It was small class with couple of properies,why we can't use data annotations to handle validations? So that we don't end up with thousands of classses in a project.

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      You missed the point

    • @microtech2448
      @microtech2448 ปีที่แล้ว

      @@MilanJovanovicTech and what is that point? You mentioned in the end that it is one of the options available but still want to know if data annotations could be used for validation instead of value objects?

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      @@microtech2448 Do you want to pollute your domain objects with data annotations? How/when will you invoke validation?

    • @microtech2448
      @microtech2448 ปีที่แล้ว

      @@MilanJovanovicTech I wouldn't want to fill my project with tons of value objects either. I just wanted to know if there is other way to validate properties, and I thought of data annotations as an option but may be that wouldn't work in this case

  • @David-rz4vc
    @David-rz4vc ปีที่แล้ว +1

    Does this mean first name will be it's own table if we use entity framework?

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

    Hi @Milan, In realtime example where Value object will be helpful?

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

      They're used for domain modeling and expressing invariants/business rules

  • @pigrebanto
    @pigrebanto 14 วันที่ผ่านมา

    Milan you are a great guy. But let me tell you. Here we are saying that those initial 3 lines of code are not safe (?), not robust (?), not elegant (?) thus I start to write more than 100 LOC quite complex to read and write to justify the greateness of my code. Do you think companies are willing to pay that? How much time do I need to spend? What about an alternative solution using maybe no more than 10 LOC instead? I think software engineering is partially theoretical (good for courses, teaching and video) and partially (the most important part) is to solve real problem in production with still good enough code. Sorry just my 2 cents.

    • @MilanJovanovicTech
      @MilanJovanovicTech  14 วันที่ผ่านมา +1

      Sadly, the value behind most of the DDD ideas are missed because devs can't see the value in simple examples. And if you use complex examples, you can't explain the concept in a simple way. Go figure.

  • @DeejayWazzouille
    @DeejayWazzouille 2 ปีที่แล้ว +1

    Hi Milan, great video !
    I wondering if there is a simple way to do the same but when your firstname max length can be different depends on the model.
    ex :
    class Person, firstname max lenght is 100
    class Company, firstname max length is 200
    regards,

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Create an abstract *NameWithMaxLength* ValueObject, and inherit from it for different lengths? 🤔

  • @paulbarton2280
    @paulbarton2280 2 ปีที่แล้ว +1

    You do not implement implicit type conversion in your Value Objects to maintain argument clarity?

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Hey Paul! 😁 I sometimes implement the implicit conversion from Value Object to primitive type, but I didn't find it useful to show in this video. Do you think it would've been valuable to add?

    • @paulbarton2280
      @paulbarton2280 2 ปีที่แล้ว

      @@MilanJovanovicTech I generally use them, however I like how not implementing them enforced strongly typed static factory prams in your example. It left no way to mistakenly pass email instead of first name for example; Something that is difficult to unit test. In short, I guess mentioning them with pros and cons could have been useful.

  • @injypal
    @injypal 2 ปีที่แล้ว +1

    Please make video on aggregates and aggregate root

  • @vmamore
    @vmamore 2 ปีที่แล้ว +1

    Hey Milan, thanks for the video!
    Can you share the code of the project?

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      Hey, you can get access to the source code by supporting me on Patreon. However, feel free to reach out to me on LinkedIn and I will give it to you for free this time :)

  • @aseneda
    @aseneda 2 ปีที่แล้ว +1

    Hey! Excellent content Milan. How about to use Struct for comparison needs than Class for Value Objects?
    Keep going.
    Regards

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +2

      You won't have inheritance. That will prevent you from doing generics with ValueObject as the base class, which can be very useful.

    • @aseneda
      @aseneda 2 ปีที่แล้ว

      I got your point.
      Are there some situations where you don’t need inheritance (and a property Id)? Date, e-mail or currency

  • @justgame5508
    @justgame5508 ปีที่แล้ว +1

    I built these with structs, it always annoyed me though that there was no way to 100% remove potential ambiguity from whatever uses the object. If you go with your approach then (in theory) someone can just pass null! so null check is still technically needed in the function and with the struct approach it can’t be null, but all structs have a default parameterless constructor so the validation can be ignored. Have not found a way around this yet and although it’s probably very unlikely someone passes null! to a function then is surprised at a null reference exception it still bothers me there is no way to make this compiler guaranteed to be safe

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Maybe one day we'll have this

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

      Dont record structs solve this?

  • @alexisfibonacci
    @alexisfibonacci 2 ปีที่แล้ว +1

    For the functionality around equality and related matters, will you have benefitted from just making the base ValueObject class a record?

  • @oscartolaba8093
    @oscartolaba8093 ปีที่แล้ว +1

    Whats the shortcut for search method?

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

    can we use records instead of inherits fron ValueObject class?

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

    how to do a mny to many where one of the classes is a value object in .net6?

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

      Check the EF docs

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

      Hi, I am getting this error, I defined the Id as auto generated primary key , when i run the seeding i get this issue:
      The seed entity for entity type 'Article' cannot be added because a non-zero value is required for property 'Id'. Consider providing a negative value to avoid collisions with non-seed data.
      @@MilanJovanovicTech

  • @mahmoudalballah3387
    @mahmoudalballah3387 ปีที่แล้ว +1

    Where can I find this project?

  • @m.waheedanwar7105
    @m.waheedanwar7105 4 หลายเดือนก่อน

    You are best

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

    why not create the ValueObjects as Records?

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

      It'll look similar if you need to add a private constructor. Another issue with using records is avoiding value object invariants using the with expression.

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

      @@MilanJovanovicTech thanks for replying Milan! Your content is excellent! I'm addicted to your playlist.

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

      @@MilanJovanovicTech I got it. So the consumer would basically be able to bypass the validation logic in the fabric method applying a change using the with expression. That's a very valid point!

  • @XMaverick20
    @XMaverick20 ปีที่แล้ว +1

    Is anyone using this in production code?

  • @vitahvitah5823
    @vitahvitah5823 2 ปีที่แล้ว +1

    I would like to watch a code source...

  • @LordErnie
    @LordErnie ปีที่แล้ว +1

    If you want value objects, create structs, ref strucs, or record structs. It hurts to see you make abstract and complicated code for something that could be taken care of in a much cleaner way.

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      There are some nuances there

    • @LordErnie
      @LordErnie ปีที่แล้ว

      @@MilanJovanovicTech Could you elaborate on that? I would love to know what the pros and cons would be.

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      @@LordErnie This is good reading on the topic:
      enterprisecraftsmanship.com/posts/net-value-type-ddd-value-object/

    • @LordErnie
      @LordErnie ปีที่แล้ว

      @Milan Jovanović An interesting read for sure. However, it does state something that is in a way untrue due to irrelevance. It states that structs can not have custom constructors, which is true as far as I'm aware. But later on, it says that this is a bad thing because you want an exception to be thrown whenever an object is initialized in a non correct way, and that structs can not achieve this in a proper way. This is false. C# had included the required keyword, which forces an object to have any fields or props with set keyword to be assigned during either construction or later via the object initializer.
      In the article it never gives any other reason that validates the leaving out of structs as DDD value objects enough to actually consider it. Am I missing something here?

  • @guard13007
    @guard13007 2 ปีที่แล้ว +1

    Setting a 50 character limit on a name is barbaric. :\

  • @darkogele
    @darkogele 2 ปีที่แล้ว +2

    So much code for one string hmmm don't like it boyy 🤣🤣

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว +1

      Well if it is "just a string" then there is no point to bother with this.
      If the string actually represents something complex, then it's an entirely different matter.

  • @HenrryPires
    @HenrryPires ปีที่แล้ว +1

    I find this way to work horrible. You end up with hundreds of classes. Memory allocation is so high.
    The constrains are also horrible, you have logic spread everywhere, if you need a extra validation depending on the context it's just impossible. Please follow the KISS principle
    what prevents you from by mistake doing this?
    var firstName = FirstName.Create(request.LastName)
    Don't get me wrong, i like your videos, i just think this specific one spread a VERY bad idea of how to do your code. For the fist time i have to leave a dislike on one of your videos

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Hopefully we catch silly mistakes like that in development, PR reviews, testing. It's not a problem of value objects. You can make the same mistake assigning to a property, or passing that value to the constructor.

    • @HenrryPires
      @HenrryPires ปีที่แล้ว

      ​@@MilanJovanovicTech that's my point, value objects do not prevent that kind of mistakes and introduce an extra layer of complexity, with no real gain

  • @adambickford8720
    @adambickford8720 2 ปีที่แล้ว +1

    This would be trivial in java Spring. A few annotations and it does all the data binding/validation for you:
    @Value
    class User {
    @NotNull
    @Size(min = 1, max = 50, message = "First name must be between 1 and 50 character")
    String firstName;
    @NotNull
    @Size(min = 1, max = 50, message = "Last name must be between 1 and 50 character")
    String lastName;
    }
    Fully immutable, encapsulated, value-based equality, etc. like you're achieving with all the wrapper types in a few annotations. You also get a sane dev experience where you can just concat/arithmetic/logic values w/o having to unwrap everything first. In the case where you hand craft a user you can just pass the object off to a `Validator` and w/1 line get all of your constraint errors (which can be in a constructor, factory, etc). It really is that easy!
    I get the type-safety argument; the juice just isn't worth the squeeze. Type safety is proving its well-formed but what you really need is proof its correct. Your tests should be verifying you didn't call `getFirstName()` when it should have been `getLastName()`, at a fraction of the cost! We're way beyond the 80/20 point IMO.

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 ปีที่แล้ว

      I tend to heavily avoid annotations (attributes in C#) in my Domain layer. They obscure domain logic. I would rather make it explicit with a little bit more work. It's a tradeoff that is worth it to me.

  • @haruundk
    @haruundk ปีที่แล้ว +1

    How would a configuration look like in Entity Framework?
    I've tried with:
    builder.OwnsOne(p => p.FirstName, opt => opt.Property(p => p.Value).HasColumnName("FirstName").IsRequired() );
    I am not able to do _dbContext.Users.FirstOrDefaultAsync(e => e.FirstName ==Firstname) i have to do _dbContext.Users.FirstOrDefaultAsync(e => e.FirstName.Value ==Firstname.Value) for it to work?
    Do i have to do it the last way? meaning select .Value every time? :/

    • @MilanJovanovicTech
      @MilanJovanovicTech  ปีที่แล้ว

      Unfortunately that's how you have to do it if you want to use value objects :/