I only recently discovered your channel, Mr Horvat, but I must say -- I believe you are already my fav developer-instructor. Your content is higher-level but pragmatic, and you are EXCELENT with your presentation. You do have a strong accent which, in your case -- actually enhances your clarity. Or perhaps you are taking great care to speak deliberately and clearly? Regardless, I love how you present your topics, I appreciate the work you put into these and THANK YOU for sharing this excellent instruction and advice.
@zoran-horvat Жыл бұрын
Thanks!
@detaaditya623718 күн бұрын
I agree with you a lot. What I’m struggling with right now is that a lot of people around me are too used to primitive types. This habit actually hurts the quality of our code that weird functions and hacky one-time types emerge everywhere. I will show this video to my team to raise their awareness
@zebcode Жыл бұрын
Where has this video been all my life? 🎉
@zoran-horvat Жыл бұрын
In my drawer :)
@zebcode Жыл бұрын
@zoran-horvat I think there were two good lessons in this video. 1) Don't obsess over primate types for structured data. 2) Small changes, otherwise you end up in a mess and can't remember what problem you're even tying to solve.
@makemeafirewall7 ай бұрын
It shows that you are a real professional
@metallixbrother Жыл бұрын
I am curious about what should qualify as a "domain model", and what is adding additional unnecessary complexity to the code. By way of example; Let's say that I have a Fraction class that takes in two integer values, a Nominator and Denominator. Of course, I need the Denominator to be a non-zero value, otherwise we'll end up dividing by zero! Now, I could use a smart constructor to perform the validation on the denominator value, but I wonder if it would be more appropriate to instead have a NonZeroInt (or in C# 11+, NonZero where T : INumber, using an instance of NonZero) class, which itself has a smart constructor and performs that validation, which allows me to simply pass a normal integer value for the numerator and a NonZeroInt in the denominator. Is this approach overkill? Because I'm not sure if there is necessarily anything domain worthy about a numerical value not being equal to zero, but it would allow me to abstract away a lot of instances where I would otherwise be performing the same validation checks in other parts of the code.
@zoran-horvat Жыл бұрын
Domain model is the model of the problem domain - a projection of reality to a finite set of dimensions. Anything that has a place in customer's requirements is almost certainly qualified to be part of the domain model. DDD is going one step further and introduces ubiquitous language, requesting both customers and code to name things the same.
@metallixbrother Жыл бұрын
@@zoran-horvat thank you kindly for your input. From your response, am I heading in completely the wrong direction with my example?
@zoran-horvat Жыл бұрын
@@metallixbrother Not necessarily - if what you are saying is the domain requirement, then it should be implemented as a domain type. Namely, if there is importance in a certain number never being zero, then that is a candidate for an explicit model.
@justingodesky5912 Жыл бұрын
I'm glad KZbin suggested your videos. Instant subscribe
@VoroninPavel Жыл бұрын
The most annoying thing with rich domain modelling is that it almost immediately hits the wall with EF. It's much better these days compared to what we had before, but still lacking some critical features. And then we have a choice either to introduce conversion layer from domain entities to db DTOs, or constrain ourselves to what is supported.
@zoran-horvat Жыл бұрын
True, it is really demotivating ...
@srivathsaharishvenk Жыл бұрын
Great content, better than most other c# channels :) , The only small suggestion or conern I have is the usage of implict operator for Citation instead of proper static factory method, but I also understand, this is the most obvious way to not break any existing code
@zoran-horvat Жыл бұрын
That was precisely the idea - to try not to cause any change in any other class before that step ends and we verify that nothing is broken. The final goal is to delete the conversion operator when the entire redesign is over.
@codingbloke Жыл бұрын
Excellent video. Looking forward to the next one. String especially can be painful. A lot of "strings" are actually "html strings", hence a + b should not necessarily be a simple concatenation. So many urls are often stored in strings, instead of a Uri. There many, many "types" of string and not understanding how to combine "types" of string is huge cause of bugs and even security issues (see "SQL strings").
@brendonlantz5972 Жыл бұрын
This is great advice, and given the context of a c# application with buisness logic this seems like a good default practice in most cases. Your point about the inflexibility of them is well taken. However if applied more generally I would say primitives have some advantages depending on the circumstances. 1, Depending on the language using primitives versus objects can have performance implications (stack/heap allocations, string copying, etc). 2, Primitives are standard in the language and understood without needing to reading specific implementation details (although for a counterpoint, primitives can be more vauge - what is this string doing exactly?). 3, And they can have a less pronounced benefit in other use cases /programming paradigms (a struct of one primitive versus a primitive in a data driven application where the logic in not contained in the object). I think one could argue the addition of primitives to that struct could be anologous to extending a class but I think the main point is are you returning a value/object that is extensible to your needs. Just thought I'd share some ideas, I work on predominantly low level c++ code so that's where I'm coming from.
@slowjocrow64512 ай бұрын
Hi Zoran, do you have any video on creating a Result type? ResultOrError or something like that? I'm trying to minimise throwing exceptions. Thanks!
@zoran-horvat2 ай бұрын
@@slowjocrow6451 Not now. There is a script in the queue and I will record it at one point.
@tarsala1995 Жыл бұрын
As always, great video. One thing that might be a good content for the future video is to use more strict access modifiers for classes to communicate with a dev more clearly about the intentions but also provide some encapsulation so that classes like Citation cannot be used in, let say, Infrastructure layer.
@zoran-horvat Жыл бұрын
I am not a fan of hiding models. What would be the reason not to allow persisting Citation instances, for example? When I want to indicate that some models are better used indirectly, I place them into a nested namespace. The parent namespace then provides all the means necessary to declare and create instances, but the models themselves are public like any other. That for one thing dismisses the question of how to test them.
@7th_CAV_Trooper9 ай бұрын
I advocate this approach, but it can create issues with serializing JSON input/output in aspnet apps. Nothing that can't be overcome, but extra work is required.
@zoran-horvat9 ай бұрын
Serialization should not be performed to and from domain models. Simply, the models have other, way more complex responsibilities to think about. Lift is much simpler if you do serialization from DTOs.
@mrspandex3 Жыл бұрын
I like the pragmatic way you've approached this problem. However, when you were splitting into segments, I would have made the backward compatible string constructor split on commas rather than treat the entire list as a single segment. Treating it all as a single segment seems prone to create UI-visible bugs if not all the sources of string initialization have been addressed.
@zoran-horvat Жыл бұрын
That is the next step. I tend to make steps as small as possible, so that at every step it is more than obvious that the code before and after the change will produce the same effects. Every discrepancy means that the last change - usually no more than a couple of lines of code - is to blame. Let me give you a hint how that can help. The first time I split into multiple segments in this demo, there appeared strange white spaces in the UI. It turned out that the browser rendered whitespace around spans unless they are glued together in the HTML. I only do frontend when I must (which turns out to be in videos only...) and then I make subtle mistakes like that one. However the very fact that the only thing I did since the last time application looked fine, was to generate spans for individual segments. That helped me pinpoint the problem in a second, literally.
@insomniac2025 Жыл бұрын
Really love your content!
@brotherzeroАй бұрын
Probably a dumb question but why is the Add method creating a new instance of Citation instead of just adding the segment to the current instance?
@zoran-horvatАй бұрын
Because the design is immutable. There is no reason that would force it to be mutable, so my default decision is to keep it immutable.
@d2pgoon86 Жыл бұрын
Zoran, why do you not use pattern matching with switch expressions instead of nested ternaries?
@zoran-horvat Жыл бұрын
If it looks like a chain of Boolean tests, then I prefer a chain of ternary operators. A personal preference. I have nothing against using switch expressions in the same situation, especially because switch expressions are matching cases in order of appearance, as documented.
@bulsond Жыл бұрын
Thank you, an interesting lesson. But I would like to see the work of the new class (Citation class) not in a ready-made application, but in unit tests written for it. I would like to see the use of unit tests as a class design tool.
@zoran-horvat Жыл бұрын
There will be a similar video soon, where I plan to show the technique of testing class postconditions. In the meanwhile, you may watch the previous video where tests are constructed against the class's contract, avoiding to depend on its implementation: kzbin.info/www/bejne/pqCcq5yDmNN0jck
@bulsond Жыл бұрын
@@zoran-horvat Thanks.
@michaelkoss Жыл бұрын
I'm very excited for the next video. Subscribed!
@VoroninPavel Жыл бұрын
Why not readonly record struct CitationSegment?
@zoran-horvat Жыл бұрын
The primary reason is that there are going to be multiple variants later (that is already a certainty) and therefore it cannot be a struct. Otherwise, if this were the only variant with only a single reference inside, then declaring it as a record struct would indeed be a viable performance optimization.
@stefanmilovcevic9110 Жыл бұрын
From now on we should add this in every application by default 🤣
@ivandrofly Жыл бұрын
Thanks :)
@LordErnie Жыл бұрын
I still don't get the whole primitive obsession thing. It's a string. What's wrong with a string? If you want to know what it is, give it a proper name and documentation, right? The only reason why you make a new data structure is when you have values paired with behaviors or if there are some values that belong together. There is nothing wrong with primitives. There is something wrong with devs writing helper classes to infinity, not properly documenting stuff, treating values with implicit behavioral magic. Your example uses a citation. Well, by the looks of it it's not just string. You contain segments, so it can't just be a string looking at the structure of the data. I think people are to obsessed with making everything a wrapper just to get rid of primitives. Sometimes a name is a string, and age an unsigned short (for the love of Code Jesus, use date of birth). It's when you attach behaviors or rules to the value that makes it a candidate for its own type. This, however, introduces the problem of not letting data just be data. It also raises questions about stuff like validation. What logic should be in a primitive wrapper, and what shouldn't? Devs are obsessed with primitive obsession. The term is used in places where it doesn't make sense. People who just learned that a primitive should be wrapped, and that's it. Those people are wrong. What you do in the video, however, is a perfect example of proper primitive obsession and how to solve it. But I still don't get what choices you made logically outside of what I mentioned in this post. Maybe you can help me out? Disclaimer, I'm still a student, so don't take anything I say for granted.
@IronDoctorChris Жыл бұрын
Your experience might be skewed by being a student and/or watching a lot of videos like this. In my experience in the industry, 90% of code is way too primitive obsessed. You can for sure go too far in the other direction but I think this is less common
@zoran-horvat Жыл бұрын
Thank you for this detailed analysis. I think you are right that it should not be the goal to just wrap the primitive types - and it is not. With this video, I have tried to emphasize the importance of domain modeling. When modeling is done right, primitive types will stand in the place where they belong. You will see primitive obsession when there is domain-related behavior implemented at the place where a primitive type is used. Another typical symptom is when a developer introduces ad hoc DTOs to pass multiple values together, failing to give them higher meaning. As I said in the video, there are two major consequences of misusing primitive types in a domain model. One is bugs, usually coming from a mismatched assignment or misinterpretation of a primitive value. The other is inability to implement a feature because other accompanying values were lost in the process. One should also not forget that implementing behavior at the calling site is the source of the whole range of other problems - code duplication, unwanted dependencies, rigidness and outright bugs. The bottom line is that primitive obsession is a real thing. It does happen, and it does cause damage in application development.
@anm3037 Жыл бұрын
You are absolutely 💯 right
@chris-pee Жыл бұрын
@@zoran-horvat "One is bugs, usually coming from a mismatched assignment or misinterpretation of a primitive value" I think that's why you shouldn't implement the implicit operator on your Citation, it's too easy to pass any string when a Citation is expected.
@zoran-horvat Жыл бұрын
@@chris-pee That operator is an intermediate step while the feature is being fitted in. Once all consumers are transformed to not use strings, we remove that operator. That technique is part of iterative coding and I will explain it in greater depth in the follow-up video.
@muskyoxes Жыл бұрын
Joke's on you, strings aren't primitive in my language
@zoran-horvat Жыл бұрын
Don't be too literal. Any type that has no structure is primitive from the modeling point of view. Conversely, even a seemingly primitive type that does have a structure is not primitive. In that light, string is primitive and date is not, even though their representation might indicate the opposite.