How to Use Value Objects to Solve Primitive Obsession

Поделиться
HTML-код
  • Опубликовано: 12 янв 2025

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

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

    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!

  • @EmptyGlass99
    @EmptyGlass99 2 года назад +2

    Your explanations are very comprehensive and logical.

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

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

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

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

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

  • @matesebestyen3142
    @matesebestyen3142 2 года назад +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  2 года назад

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

    • @matesebestyen3142
      @matesebestyen3142 2 года назад +1

      Looked it up and you are right! :)

  • @adisilagy
    @adisilagy 2 года назад +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  2 года назад

      Or maybe have a separate model for Dapper?

    • @adisilagy
      @adisilagy 2 года назад +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

  • @implicat
    @implicat 2 года назад +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  2 года назад

      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.

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

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

  • @TheMostafa5000
    @TheMostafa5000 2 года назад +1

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

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

  • @prathameshshende4
    @prathameshshende4 2 года назад +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?

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

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

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

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

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

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

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

    I'm really enjoying this stuff. Nice work!

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

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

  • @ayalamac
    @ayalamac 2 года назад +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  2 года назад

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

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

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

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

      @@MilanJovanovicTech I see. Thank you

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

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

  • @PedroPaulo-if6pq
    @PedroPaulo-if6pq 2 месяца назад

    Hello Milan! Great video as always! One question: could a Value Object (not the base class) be inherited (not sealed) or even be abstract?

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

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

  • @stunna4498
    @stunna4498 2 года назад +1

    Do you normally use Guid for primary keys by default?

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

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

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

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

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

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

      @@stjepankarin not convinced at all. If you did this but let say you now need from the same service to handle for gRPC and a msg bus, you now need to validate email in 3 places, before sending to your domain logic. If its all done in the domain logic then its all done in one place.

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

  • @dilipmevada
    @dilipmevada 6 месяцев назад

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

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

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

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

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

      I think you're confusing data management with behavior

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

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

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

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

    Where can I find this project?

  • @aah134-K
    @aah134-K 2 года назад +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  2 года назад +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 2 года назад

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

    • @aah134-K
      @aah134-K 2 года назад

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

  • @haruundk
    @haruundk 2 года назад +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  2 года назад

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

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

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

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

  • @vivekkaushik9508
    @vivekkaushik9508 2 года назад +1

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 года назад +1

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

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

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

    How will FIrstName.value be stored in a Table and what column? Do we need to describe that explicitely in Configuration? and how about the vice versa, when we read data, how EF Core will know what value to put into FirstName.Value ?

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

      FirstName_Value, but configurable via Property().HasColumnName()

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

      @MilanJovanovicTech thanks!
      I also watched a video about complex property and went in that way

  • @jon-slem
    @jon-slem 6 месяцев назад

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

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

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

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

    Whats the shortcut for search method?

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

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

  • @JonathanPeel
    @JonathanPeel 2 года назад +1

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

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

    can we use records instead of inherits fron ValueObject class?

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

  • @injypal
    @injypal 2 года назад +1

    Please make video on aggregates and aggregate root

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

    Good stuff.

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

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

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

      Check the EF docs

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

      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

  • @serjioto88
    @serjioto88 3 месяца назад

    sorry how to move the solution explorer to the left?

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

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

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

  • @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 RUclips, 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!

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

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

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

    Is anyone using this in production code?

  • @rcarubbi
    @rcarubbi 6 месяцев назад

    why not create the ValueObjects as Records?

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

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

    • @rcarubbi
      @rcarubbi 6 месяцев назад

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

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

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

  • @vitahvitah5823
    @vitahvitah5823 2 года назад +1

    I would like to watch a code source...

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

    You are best

  • @LordErnie
    @LordErnie 2 года назад +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  2 года назад

      There are some nuances there

    • @LordErnie
      @LordErnie 2 года назад

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

    • @MilanJovanovicTech
      @MilanJovanovicTech  2 года назад

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

    • @LordErnie
      @LordErnie 2 года назад

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