How to Use Value Objects to Solve Primitive Obsession

Поделиться
HTML-код
  • Опубликовано: 26 июл 2024
  • ☄️ Master the Modular Monolith Architecture: bit.ly/3SXlzSt
    📌 Accelerate your Clean Architecture skills: bit.ly/3PupkOJ
    🚀 Support me on Patreon to access the source code: / milanjovanovic
    Value Objects are types defined only by their values. Two value objects are considered equal if their values are identical. Value Objects can be used to solve primitive obsession, which occurs when we use primitive types to represent more complex concepts.
    Join my weekly .NET newsletter:
    www.milanjovanovic.tech
    Subscribe for more:
    ruclips.net/user/MilanJovano...
    Chapters
    0:00 What is Primitive Obsession
    2:24 Introducing Value Objects
    3:51 Supporting structural equality
    6:24 Implementing a Value Object
    7:43 How to enforce constraints
    10:11 Using our Value Object
    12:40 What we get with Value Objects
  • НаукаНаука

Комментарии • 192

  • @MilanJovanovicTech
    @MilanJovanovicTech  Год назад +5

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

  • @eddypartey1075
    @eddypartey1075 7 месяцев назад +4

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

  • @fieryscorpion
    @fieryscorpion Год назад +17

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

  • @YuriWithowsky
    @YuriWithowsky Год назад +4

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +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.

  • @joshem32
    @joshem32 Год назад +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  Год назад

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

    • @joshem32
      @joshem32 Год назад

      @@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 11 месяцев назад +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.

  • @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  Год назад

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

  • @kinggrizzly13
    @kinggrizzly13 Год назад +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  Год назад

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

    • @kinggrizzly13
      @kinggrizzly13 Год назад +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 7 месяцев назад +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.

  • @dimitricharles9784
    @dimitricharles9784 Год назад +1

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

  • @Rev1211b
    @Rev1211b 11 месяцев назад

    I'm really enjoying this stuff. Nice work!

  • @yazanshakhshir3049
    @yazanshakhshir3049 11 месяцев назад +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.

  • @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! :)

  • @eduugr
    @eduugr 11 месяцев назад

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

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

  • @TheMostafa5000
    @TheMostafa5000 Год назад +1

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

  • @MarvijoSoftware
    @MarvijoSoftware 10 месяцев назад +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

    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  Год назад +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 Год назад +1

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

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

  • @Skillamu
    @Skillamu Год назад +1

    Great video!

  • @Mefhisto1
    @Mefhisto1 Год назад +2

    Good stuff.

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

  • @majormartintibor
    @majormartintibor Год назад +1

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +1

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

    • @majormartintibor
      @majormartintibor Год назад +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.

  • @xBalaDeCanhaox
    @xBalaDeCanhaox Год назад +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  Год назад

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

  • @100kshooter5
    @100kshooter5 Год назад +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  Год назад +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 Год назад

      @@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 Год назад +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.

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

  • @evilTano
    @evilTano Год назад +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  Год назад

      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

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

  • @CousinAnthony
    @CousinAnthony Год назад +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  Год назад

      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.

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

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

  • @RoaringOrange
    @RoaringOrange Год назад +2

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      Why do you think this is the case?

    • @RoaringOrange
      @RoaringOrange Год назад

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

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

  • @DeejayWazzouille
    @DeejayWazzouille Год назад +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  Год назад

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

  • @stunna4498
    @stunna4498 Год назад +1

    Do you normally use Guid for primary keys by default?

  • @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 :)

  • @Philippe42460
    @Philippe42460 10 месяцев назад +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  10 месяцев назад +1

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

  • @dilipmevada
    @dilipmevada 23 дня назад

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  22 дня назад

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

  • @jesusantoniomartinezhernan2791
    @jesusantoniomartinezhernan2791 Год назад +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  Год назад

      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 Год назад +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  Год назад

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

  • @JordiC354
    @JordiC354 5 месяцев назад

    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  5 месяцев назад

      I think you're confusing data management with behavior

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

  • @oscartolaba8093
    @oscartolaba8093 Год назад +1

    Whats the shortcut for search method?

  • @makinibryan
    @makinibryan 9 месяцев назад +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  9 месяцев назад

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

  • @JANDRESCR
    @JANDRESCR 5 месяцев назад

    can we use records instead of inherits fron ValueObject class?

  • @paulbarton2280
    @paulbarton2280 Год назад +1

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      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 Год назад

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

  • @vmamore
    @vmamore Год назад +1

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      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 :)

  • @mahmoudalballah3387
    @mahmoudalballah3387 Год назад +1

    Where can I find this project?

  • @aseneda
    @aseneda Год назад +1

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +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 Год назад

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

  • @brechtlaitem
    @brechtlaitem Год назад +8

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +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 Год назад +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  Год назад +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 Год назад +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 Месяц назад

      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?

  • @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 7 месяцев назад

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

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

  • @JonathanPeel
    @JonathanPeel Год назад +1

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

  • @David-rz4vc
    @David-rz4vc Год назад +1

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

  • @maxpuissant2
    @maxpuissant2 Год назад +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  Год назад

      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 Год назад

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

  • @m.waheedanwar7105
    @m.waheedanwar7105 2 месяца назад

    You are best

  • @syedib
    @syedib Год назад +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  Год назад

      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.

  • @injypal
    @injypal Год назад +1

    Please make video on aggregates and aggregate root

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

  • @TimurBabyuk
    @TimurBabyuk Год назад +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 RUclips, including railway oriented programming

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +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!

  • @jon-slem
    @jon-slem 22 дня назад

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  21 день назад

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

  • @somyarathee
    @somyarathee 5 месяцев назад

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  5 месяцев назад

      Check the EF docs

    • @somyarathee
      @somyarathee 4 месяца назад

      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

    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 😅

  • @10199able
    @10199able Год назад +2

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

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

    • @10199able
      @10199able Год назад +1

      @@MilanJovanovicTech There is literally string50 type in it!

  • @microtech2448
    @microtech2448 11 месяцев назад +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  11 месяцев назад

      You missed the point

    • @microtech2448
      @microtech2448 11 месяцев назад

      @@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  11 месяцев назад

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

    • @microtech2448
      @microtech2448 11 месяцев назад

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

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

  • @alexisfibonacci
    @alexisfibonacci Год назад +1

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

  • @XMaverick20
    @XMaverick20 Год назад +1

    Is anyone using this in production code?

  • @vitahvitah5823
    @vitahvitah5823 Год назад +1

    I would like to watch a code source...

  • @rcarubbi
    @rcarubbi Месяц назад

    why not create the ValueObjects as Records?

    • @MilanJovanovicTech
      @MilanJovanovicTech  29 дней назад +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 29 дней назад

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

    • @rcarubbi
      @rcarubbi 29 дней назад

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

  • @artemivanov2141
    @artemivanov2141 2 месяца назад

    Why don't just do FirstName as struct?

  • @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 Год назад +1

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

  • @darkogele
    @darkogele Год назад +2

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +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 10 месяцев назад +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  10 месяцев назад

      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 10 месяцев назад

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

  • @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 :/

  • @adambickford8720
    @adambickford8720 Год назад +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  Год назад

      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.