Get the source code for this video for FREE → the-dotnet-weekly.ck.page/rop Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
I was using it and its a clean and nice way of doing complicated logic. Unfortunetly for most of the team this was not welcomed as considered too complicated. Its so hard to start thinking in functional way these days
I have the same experience. ROP and other functional concepts are actually quite hard. Once dookie hits the fan and your team is under time pressure, devs start cutting corners and finding workarounds. The ROP model that was initially so beautiful and pure suddenly becomes everyone's nightmare full of nasty hacks. Imho this approach just doesn't stand time that well...
Horrible. Mixing function and OO causes more problem and most of the times devs bringing it to the code base are literally trying to resolve problems that doesn’t exist.
The problem with using the Result pattern in C# is that the language doesn't give us the syntax to enforce it. Historically C# reference types have been "weak" in the sense that a string could be a (string or null). That has been fixed with nullable reference types. Now a value that could be null must be explicitly defined as being nullable (string?). But functions are still "weak" contracts. A function that returns a string can either (return a string OR raise an exception). Result doesn't change that. A function that returns Result can still (return Result or raise an exception). We need some mechanism at the language level where the caller knows that when a function returns Result, there is no possibility of an exception. That would be a "strong" contract. "async" kind of does this for "Task". An async Task function will only raise the exception when it is awaited. Unfortunately, a caller can't be confident that a function that returns Task is an async function that won't raise an exception. Only the async keyword creates the state machine to wrap the exception, but a function can return Task without using async, which could raise an exception directly. Perhaps this might be done with source generators or IL weaving? Are there any Result implementations that provide this functionality of "strong" contracts?
I don't think you will find what you're looking for in the open source world. Best we can do is accept that exception "can" happen, and we can deal with them accordingly.
@@MilanJovanovicTech I do like the Result pattern & think it's a better way to explicitly declare that a function may return a Error, rather than the goto-ish behaviour of Exceptions. But currently it feels "ugly" in C#. Standard C# functions could (return value OR raise Exception. Using Result leads to functions that could ((return T OR Exception) OR raise Exception) Is that really an improvement?
If we follow the principle that functions are opaque, and we only interact with them via the contract of their signature (inputs -> outputs), then we must carry on wrapping everything in try...catch even when we have a functional pipeline, because we don't know if any of those functions in the pipeline will raise an exception instead of wrapping the exception in a Result.
It's nice once you get used to it. Everything is immutable and in your tiny scope, so you don't have to keep as many 'moving parts' in your head. Each 'prefix' gives you an idea of what to expect: `Tap` will be a side effect, `bind` could short circuit, etc. It's not like imperative programming where "oh boy, we just set a boolean up above to be consumed down below... maybe".
Definitely there's a learning curve here. Although I didn't find it too difficult. I first worked with RxJs in Angular a while back, before discovering ROP. And it's almost identical.
@@sushantkhare8467 why are so many people saying c# is not designed for it? Are you talking about the current generation of junior devs not having the cognitive capacity to understand that you don't need a strict approach? Go benchmark it if the structure irks you. This is literally how you set up your host, DI container, chaining linq calls, and don't get me started on generating expression trees. This is a common look in many implements of c# programs. What you mean is, c# can do this, but nobody does it because they aren't thinking of ways to leverage code.
@@user-jc6pe2bp1y they have to have just started a course in programming with morons for professors thats the only way people are coming up with these statements
My suggestion is to add some sort of container that is passed to each call so the intermediate functions are able to write to that container, such as their error, like failed validation. That way, you can couple the error with the call within the same parameter set or something.
If there is any error thrown by any method, processing the rest may not be good in most cases. I implemented a fluent monad years ago that checks if an exception or error has already occurred. If yes, the method does not execute rest of the statements. This is done inside the monad, so the method using this, has to check if the exception flag is turned on, at the end and then fetch the error.
I would say this pattern is more suitable for algorithm procedure implementations rather than structure. Keeping the OOP paradigm for structural code and FP paradigm for implementations code is my goto approach
@@MilanJovanovicTech structural code would be the design of the abstractions interactions themselves (object behaviour patterns) while implementations would be the private state that goes into those abstractions (the procedure themselves). The reasoning behind my idea is because most of the time you want to keep abstractions simple enough in terms of maintainability whereas procedures can grow very complex and this "railroad" approach basically removes the technical complexity from the business one therefore allowing one to focus only on the business logic. Let me know your thoughts as well.
I use a simplified implementation of an Either class in my code (more for returning errors rather than throwing exception). I don't believe either way around, in your example, is more or less readable, I do believe there is more complexity in the final result, but that's just my opinion. I think what you have done is great. What perplexes me more though is that we need to consider pushing an OO language into a functional paradigm in the first place. It's no better, it's just a different approach. There are programming languages that already work functionally, if that's what is required, then perhaps use one of those instead, even MS has one that can be used in VS - F#. Yes, I know you're going to use the LINQ argument, and yes it is functional, but that's basically just extensions over IEnumerable and no reason to try and force everything else into that functional way of thinking. It sounds like I'm disagreeing with your approach, that's not my intent, I think what you've done is good and useful to some, not really for me, and, I simply don't agree that it should be introduced into an OO language.
@@MilanJovanovicTech I understand that, that wasn't really my point. My point was that while I use some functional paradigms in my code (albeit simplified versions), I don't agree that incorporating functional styles inside an OOP language is necessarily a good idea.
@@MilanJovanovicTech Something like that? public static async Task ExecuteAsync(this Result result, Func executor) where TResult : Result { return result.IsSuccess ? await executor(result.Value) : result.Value as TResult; } I'm wondering if there's a nicest way to do that and use in the middle of the flow, in that way the async have to be the last in execution stack.
Thx for the source code. I am doing everything from the scratch and put everything together when possible. That way I can check if I did everything right or if I'm missing something. Thx. again.
cool and all, but you had to write 3 funciton definitons, besides the extension methods. I'll just keep using guard clauses and thorwing. I could get used to it though, if I had to, for example in rust.
@@MilanJovanovicTech no, I meant the first three functions you wrote besides the extension methods. It's a nice way to program for sure, but it makes it harder readable for most devs (including me). And you also said it's slower because of extra allocations, so.. I am just learning about it to not get left behidn if this trend catches on :D
@@MilanJovanovicTech will it look much uglier if we pass all dependencies as arguments when chaining methods. Imagine you will pass logger in all of them
I've seen you previous videos on the topic and I love this. And I'm yet to find a convincing argument against it - either at work or in this comment section. Here's a question: why you have all these functions on Result as extension methods and not just part of the Result class ?
I find it more flexible to organize with extension methods. It starts making even more sense with a Result and Result object, which is how I usually do it. Most folks against this approach are unfamiliar with FP paradigm, so it feels strange. 😅
The case against it is where the scope of error handling is wider than a single layer and there are several levels of the call stack separating the error from the handler. This pattern is useful, but as with any pattern, trying to fit it into every scenario becomes unwieldy. In general, it isn't worth reinventing the wheel just to "get rid of" alternatives that already fit the purpose.
I personally don't like the Bind, TryCatch, and Tap methods. They complicate code in this ROP. I prefer how LINQ works where you call .Select, .Where, .OrderBy directly... I think that's more readable rather than introducing functions like Bind. Would that be possible in your ROP example? But I appreciate the video... It's always good to see new concepts, even if you decide they're not for you. Thanks!
@@MilanJovanovicTech So do you think there's a way to make your example work more like LINQ? I'm curious if that could be possible, but they always return a list, even if it's empty, and throw exceptions out for failures.
@@pedrosilva1437 Well, his custom methods are basically the same thing as LINQ functions, but operating on a different data type. LINQ works on IEnumerables (including things like List, which implements IEnumerable). Milan's functions mostly operate on a single value, his Result type. The main difference is in the naming. For example, Select() in LINQ is basically Bind(). It takes one type as input, and generates a (usually) different type as output. There's also Map(), which is what Python uses rather than Select(). The name "Bind" comes from functional logic, and I always found to be a horrible name for programming purposes. You could, however, simply change the name of the extension method from Bind() to Select(). Since the extension method is bound to the specific designated data type (in this case, Result), it won't conflict with LINQ's version as long as Result never implements IEnumerable. There's no equivalent in LINQ to the Tap() function. List itself does actually implement a ForEach() function which is equivalent, but it's not part of the broader LINQ, and isn't an appropriate name for a non-collection type. I would probably rename it Process(), but that's a subjective choice. TryCatch() is basically Bind/Select with exception handling. The bind and func function parameters in Bind() and TryCatch() are basically the same thing, except one returns TOut and the other returns Result. You can adjust so that they both use the same signature, and then the entire thing collapses into a single function. I'd merge them together. And then Match() just splits the OK and Error results apart to generate the final return value. That's fine as is.
How do you handle if you need a variable from two previous functions? Say that in the fourth call, you need a value from the second? If you understand what i mean.
Great video! Could you cover generic exception handling in the repository pattern in clean architecture without using the Result type? I'm keen to learn how to handle exceptions cleanly in .NET applications. if you have already video then share the link here. Thanks in advance!
I wonder if that is a production ready code or a well known approach. We are using Hellang Middleware for handling web requests, that way we can return error codes from the Application Layer to the Web layer and then return a formatted error response. To me this looks like a lot of work to put together and the benefits are quite small.
I think it should be a team-level agreement on if you'd use this. I'm not saying force this down everyone's throat. I find it a very interesting alternative to the "traditional" imperative paradigm.
I loved programming like this in Angular with RxJs, but it feels weird in C#, can't explain it better, maybe it's my brain is constantly going "this goes against every uni book you read". Would be interesting to see performance metrics for the original method vs ROP. Allocations don't matter as much to me as performance. Also would be interesting comparing the same implementation in F#, I could never fully get into it.
I'm wandering how this would look if the db and related services functions would have proper async/await implemenations. And would need to be awaited before proceeding to next function in the pipeline.
The function chains would be the same, you'd just have one await at the front, so: await ValidateLineItems() .Bind(...) .TryCatch(...) .Tap() .Tap() .Match(); The trick is just to create the async overloads for each method, that can accept and return a Task
I prefer the follow errorhandling styles (in order): 1. Result pattern 2. obj | err tuple return values (golang style) 3. exceptions (but I try to avoid it like a cat avoids water) The problem with exceptions is that they can happen anywhere down the path and they may, or may not be handled. If you don't handle them, they will explode higher up the call chain and then good friggin luck catching them all like these stupid pokemons. If you use solution 1 or 2, you force yourself handling the error immediately. Once you've gone Result pattern, you'll never go back. PS: The explanation with the drawbacks in the end was awesome as hell! Much appreciated!
I understand this completely. But maybe I am just to simple minded this seems like deeply excessive over engineering to solve a very simple problem that is very humans readable that pretty much any c# dev can eyeball in 10 seconds and figure exactly what it’s doing.
I really dislike this style of programming (in c#) Just use a global execration handler that is smart enough to know what to return to the customer. Move validation into some global handlers too. Just implement your api like a happy path (mostly). Yeah I hear you 😅 I am flaming this style in the comments hahaha
Do you also dislike LINQ? 😁 Of course I expected some heat around this topic. As was the case when I previously discussed it. What's your stance on functional programming in C# (in general)?
100% agreed. Being imperative is so much better when your code can branch (e.g. exceptions can happen). You can actually step through it without the control flow bouncing all over the place due to some declarative magic logic and without having to put breakpoints inside callbacks (ugh). I use LINQ all the time, but only if there's no branching, i.e. there are no junctions in my railroad, e.g. when projecting a data structure or when querying with EF. Exceptions and occasional result objects ftw!
@@MilanJovanovicTech I do like linq a lot. I think the main difference with linq is that its domain is well defined. That is set oriented selection methods. ROP seems to be all over the place. I don’t like to tie everything together into one long fluent syntax. Reads fine now, but how about 3 months from now? Also it is not immediately obvious where to put or add new functionality opposed to simpler transaction script style. FP in c# seems fun at start or in a green field project, but maintainers are going to hate you for it. You might even curse your younger self because he wrote some FP code that barely fitted the bill but he went ahead with it anyway because it looked cool. But 3 months from now it looks like a piece of mud … 😅
I recently joined a team where a pipeline like this was used. The problem was , the whole "old" team had left, so everyone in the team is new. This means that nobody knows what "Bind" or "Tap" means any more, and there is no documentation. This makes this code, very frustrating to maintain. We are in the process of replacing this with simpler "normal" program flow. I like the idea of this, but there are big danger flags around it, that makes me avoid it like the plague.
@@MilanJovanovicTech I gave those as examples, they are not hard to figure out, but they are not descriptive either. Tap does not tell you what it does. But again, I just used your example, in our case they were equally weirdly named, plus were then highly generic. It makes it difficult to debug, and difficult to parse. People may say it's a bad team, but I would counter that with code should be easy to parse on reading. They TryCatch is a great example, I see that method, I know exactly what it does and what it is intended for. I see Tap, I have no clue what that means, do I really have to go delve into the methods to figure out what it does. Yes, your methods are simple, ours were not. Again, I'm not attacking anything that was presented, just my experience with these pipelines.
@TheScriptPunk Your opinion suggests a punk that no one would like to work with. Firstly, if the team can handle rewriting such code and then dealing with business tasks, they are competent enough to do their job. Secondly, the code was done in some niche way (in the C# world) and it is proven by many that it is hard to follow (we write code for people, not for our ego). Thirdly, this code does not solve anything. I use the functional approach where it solves the problem, so I use 25% functional and 75% OOP. This was using some functional approach just to use a functional approach- not to solve the problem in a much simpler way. The only good thing from this video is that viewers can familiarize themselves with the functional approach, which is not the default way in C#.
I would avoid exceptions in some places only if I have to write high performant code or in some cases where it is really needed or more appropriate than throwing an exception. Just avoiding exceptions without any significant reason doesn't make code better at all.
totally agreed. Checking result is so 1970. it makes your codebase bloat. There is a reason why .net introduced exception as an alternative. I perfer to use exception for readability then optimise to use result pattern in hot paths
How about avoiding exceptions because you know what the failure is, and you know how to handle it? (If you're thinking that's not exceptional, maybe we're onto something here).
@@MilanJovanovicTech Right, if I know how to handle some situations in code I won't throw an exception of course. Exceptions is a good tool in right hands and it is only for exception situations. If you take a look at GO lang code, you can see, in some libraries, code turns into if-hell, instead of have one place to handling errors. I don't want to say that we should use exceptions whenever where possible, it is just about they are really helpful and make your code much cleaner, if use them right. It's like talking about "goto", many people hate it, but it is also really helpful if it is used right and it is even used in some places in .Net to improve performance, instead of creating additional overhead with flag variables, etc.
I'd rather implement Result with Service and handle everything in service. looks too complicated for me. coz need to introduce so many functions (btw no need to add custom methods in LINQ, they comes built in)
Lemme guess, you're a sr dev that should actually be starting as mid level instead, and don't actually code outside of work because you got your foot in the door?
Them: I don't like this approach You: might be a matter of unfamiliarity Them: let me find reasons having nothing to do with the approach to justify unfamiliarity. 😂 😂
I've heard these arguments in the past when talking about ROP 😅 So I expected as much. But still, I think it's worth talking about "different" ways to do things. Someone out there might see this, and it could really help them.
Please, just stop doing things in C# non idiomatically. The problem with Option/Result monads in C#, is that you just don't have the support from language, to use them without a pain (like in Rust or F#). And the code becames just insanely convoluted. If you want to express Option in C#, you can use NRT. If you need to do another way of error handling, just use exceptions.
@@MilanJovanovicTech that's not the point. The point is that it is not idiomatic and it's not providing that much benefit. You basically need to pattern match every result/option, regardles if you need just to return the failed result and handle happy path (which is like 95%+ of the cases when you're using these monads). You also can use panic in go for exception-like error handling. But should you?
@@ilushkinz4424 Some confusion here about expected and unexpected exceptions. This approach allows you to handle the expected cleanly using the capabilities that C# currently has. The readability, comes with experience, allows the developers to focus on the business rather than the infrastructure.
Idiomatic C# is anyway heading in this direction with the upcoming Discriminated Unions with Option and Result monads. So doesn't hurt to familiarize yourself with the concept.
Horrible. Mixing function and OO causes more problem and most of the times devs bringing it to the code base are literally trying to resolve problems that doesn’t exist.
@@arunnair7584 you've been using functional the whole time. Oo is when you encapsulate data in an object. Flow of control, var assignment, property accessing is not oo.
Get the source code for this video for FREE → the-dotnet-weekly.ck.page/rop
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
This is a video made-in-heaven for all the functional programming lovers. Nicely done!
Couldn't agree more!
fr fr
two interesting things i learned from your videos.
1. Vertical Slice architecture
2. Railway-oriented programming
Awesome, glad you're finding new ideas 😁
@@MilanJovanovicTech yes I've been working with Either monad in flutter. Keep up the good work
I was using it and its a clean and nice way of doing complicated logic. Unfortunetly for most of the team this was not welcomed as considered too complicated. Its so hard to start thinking in functional way these days
I have the same experience. ROP and other functional concepts are actually quite hard. Once dookie hits the fan and your team is under time pressure, devs start cutting corners and finding workarounds. The ROP model that was initially so beautiful and pure suddenly becomes everyone's nightmare full of nasty hacks. Imho this approach just doesn't stand time that well...
Horrible. Mixing function and OO causes more problem and most of the times devs bringing it to the code base are literally trying to resolve problems that doesn’t exist.
@@gpzim981the style it replaced wasn't even oo
You're mixing that up with general flow of control
Skill issue
True. I had faced this problem too. But if I showed them an end-to-end example of this, they usually accepted this approach.
The problem with using the Result pattern in C# is that the language doesn't give us the syntax to enforce it.
Historically C# reference types have been "weak" in the sense that a string could be a (string or null).
That has been fixed with nullable reference types.
Now a value that could be null must be explicitly defined as being nullable (string?).
But functions are still "weak" contracts. A function that returns a string can either (return a string OR raise an exception).
Result doesn't change that.
A function that returns Result can still (return Result or raise an exception).
We need some mechanism at the language level where the caller knows that when a function returns Result, there is no possibility of an exception.
That would be a "strong" contract.
"async" kind of does this for "Task".
An async Task function will only raise the exception when it is awaited.
Unfortunately, a caller can't be confident that a function that returns Task is an async function that won't raise an exception.
Only the async keyword creates the state machine to wrap the exception, but a function can return Task without using async, which could raise an exception directly.
Perhaps this might be done with source generators or IL weaving?
Are there any Result implementations that provide this functionality of "strong" contracts?
I don't think you will find what you're looking for in the open source world. Best we can do is accept that exception "can" happen, and we can deal with them accordingly.
@@MilanJovanovicTech I do like the Result pattern & think it's a better way to explicitly declare that a function may return a Error, rather than the goto-ish behaviour of Exceptions.
But currently it feels "ugly" in C#.
Standard C# functions could (return value OR raise Exception.
Using Result leads to functions that could ((return T OR Exception) OR raise Exception)
Is that really an improvement?
If we follow the principle that functions are opaque, and we only interact with them via the contract of their signature (inputs -> outputs), then we must carry on wrapping everything in try...catch even when we have a functional pipeline, because we don't know if any of those functions in the pipeline will raise an exception instead of wrapping the exception in a Result.
It's nice once you get used to it. Everything is immutable and in your tiny scope, so you don't have to keep as many 'moving parts' in your head. Each 'prefix' gives you an idea of what to expect: `Tap` will be a side effect, `bind` could short circuit, etc.
It's not like imperative programming where "oh boy, we just set a boolean up above to be consumed down below... maybe".
Definitely there's a learning curve here. Although I didn't find it too difficult. I first worked with RxJs in Angular a while back, before discovering ROP. And it's almost identical.
@@MilanJovanovicTech exactly, it reminded me of RxJs. I love declarative programming, but it looks like C# wasn't designed for it?
@@sushantkhare8467 why are so many people saying c# is not designed for it? Are you talking about the current generation of junior devs not having the cognitive capacity to understand that you don't need a strict approach? Go benchmark it if the structure irks you. This is literally how you set up your host, DI container, chaining linq calls, and don't get me started on generating expression trees.
This is a common look in many implements of c# programs.
What you mean is, c# can do this, but nobody does it because they aren't thinking of ways to leverage code.
@sushantkhare8476 what do you mean c# wasn’t designed for it? It looks like LINQ lambda notation to me. You know - the syntax that c# introduced
@@user-jc6pe2bp1y they have to have just started a course in programming with morons for professors thats the only way people are coming up with these statements
My suggestion is to add some sort of container that is passed to each call so the intermediate functions are able to write to that container, such as their error, like failed validation.
That way, you can couple the error with the call within the same parameter set or something.
Have a suggestion for the method signature for that?
@@MilanJovanovicTech yeah, similar to builder pattern that passes down the object with a this reference, or something along those lines.
If there is any error thrown by any method, processing the rest may not be good in most cases. I implemented a fluent monad years ago that checks if an exception or error has already occurred. If yes, the method does not execute rest of the statements. This is done inside the monad, so the method using this, has to check if the exception flag is turned on, at the end and then fetch the error.
Yes, this is exactly what I used to do.
I would say this pattern is more suitable for algorithm procedure implementations rather than structure. Keeping the OOP paradigm for structural code and FP paradigm for implementations code is my goto approach
What would you consider structural code and implementation code?
@@MilanJovanovicTech structural code would be the design of the abstractions interactions themselves (object behaviour patterns) while implementations would be the private state that goes into those abstractions (the procedure themselves). The reasoning behind my idea is because most of the time you want to keep abstractions simple enough in terms of maintainability whereas procedures can grow very complex and this "railroad" approach basically removes the technical complexity from the business one therefore allowing one to focus only on the business logic. Let me know your thoughts as well.
I use a simplified implementation of an Either class in my code (more for returning errors rather than throwing exception). I don't believe either way around, in your example, is more or less readable, I do believe there is more complexity in the final result, but that's just my opinion. I think what you have done is great. What perplexes me more though is that we need to consider pushing an OO language into a functional paradigm in the first place. It's no better, it's just a different approach. There are programming languages that already work functionally, if that's what is required, then perhaps use one of those instead, even MS has one that can be used in VS - F#.
Yes, I know you're going to use the LINQ argument, and yes it is functional, but that's basically just extensions over IEnumerable and no reason to try and force everything else into that functional way of thinking. It sounds like I'm disagreeing with your approach, that's not my intent, I think what you've done is good and useful to some, not really for me, and, I simply don't agree that it should be introduced into an OO language.
Result and Either are pretty much the same, a way to express a success/failure of something
@@MilanJovanovicTech I understand that, that wasn't really my point. My point was that while I use some functional paradigms in my code (albeit simplified versions), I don't agree that incorporating functional styles inside an OOP language is necessarily a good idea.
great video, thanks, can you do a video about the differences between task and valuetask? when use one of them?
Good idea
Thanks for this video!
I just think that is missing an async example of this approach
The code looks the same, you just create a bunch of async overloads that accept and return Task
@@MilanJovanovicTech
Something like that?
public static async Task ExecuteAsync(this Result result, Func executor) where TResult : Result
{
return result.IsSuccess ? await executor(result.Value) : result.Value as TResult;
}
I'm wondering if there's a nicest way to do that and use in the middle of the flow, in that way the async have to be the last in execution stack.
Thx for the source code.
I am doing everything from the scratch and put everything together when possible.
That way I can check if I did everything right or if I'm missing something.
Thx. again.
Did you manage to do it?
@@MilanJovanovicTech Yes. Enjoyed it. Became fluent in ROP :)
Very interesting video. I use result types with implicit operators for success/error types and use match but had not seen bind or tap before. 👍
So you're probably not doing ROP, but still utilizing Result 👌
@@MilanJovanovicTech yeah, this is the first I've heard of it. I'll have to give it a go. Thanks for the video.
cool and all, but you had to write 3 funciton definitons, besides the extension methods. I'll just keep using guard clauses and thorwing. I could get used to it though, if I had to, for example in rust.
You could also use a NuGet package with these abstractions built in
@@MilanJovanovicTech no, I meant the first three functions you wrote besides the extension methods. It's a nice way to program for sure, but it makes it harder readable for most devs (including me). And you also said it's slower because of extra allocations, so.. I am just learning about it to not get left behidn if this trend catches on :D
Milan how you think we can mitigate need to use DI inside extension method, do you think it will be poissble in next c# with new extensions operator ?
What does this mean
Just pass it as an argument?
@@MilanJovanovicTech will it look much uglier if we pass all dependencies as arguments when chaining methods. Imagine you will pass logger in all of them
Feels like programming with RxJs in Angular :D
Yeah, quite similar
I've seen you previous videos on the topic and I love this. And I'm yet to find a convincing argument against it - either at work or in this comment section.
Here's a question: why you have all these functions on Result as extension methods and not just part of the Result class ?
I find it more flexible to organize with extension methods. It starts making even more sense with a Result and Result object, which is how I usually do it.
Most folks against this approach are unfamiliar with FP paradigm, so it feels strange. 😅
The case against it is where the scope of error handling is wider than a single layer and there are several levels of the call stack separating the error from the handler. This pattern is useful, but as with any pattern, trying to fit it into every scenario becomes unwieldy. In general, it isn't worth reinventing the wheel just to "get rid of" alternatives that already fit the purpose.
If anybody cannot understand this code please wait and watch previous video also
Lots of people can't make sense of monads
I personally don't like the Bind, TryCatch, and Tap methods. They complicate code in this ROP. I prefer how LINQ works where you call .Select, .Where, .OrderBy directly... I think that's more readable rather than introducing functions like Bind. Would that be possible in your ROP example?
But I appreciate the video... It's always good to see new concepts, even if you decide they're not for you. Thanks!
Fair enough!
@@MilanJovanovicTech So do you think there's a way to make your example work more like LINQ? I'm curious if that could be possible, but they always return a list, even if it's empty, and throw exceptions out for failures.
@@pedrosilva1437 Well, his custom methods are basically the same thing as LINQ functions, but operating on a different data type. LINQ works on IEnumerables (including things like List, which implements IEnumerable). Milan's functions mostly operate on a single value, his Result type.
The main difference is in the naming. For example, Select() in LINQ is basically Bind(). It takes one type as input, and generates a (usually) different type as output. There's also Map(), which is what Python uses rather than Select().
The name "Bind" comes from functional logic, and I always found to be a horrible name for programming purposes. You could, however, simply change the name of the extension method from Bind() to Select(). Since the extension method is bound to the specific designated data type (in this case, Result), it won't conflict with LINQ's version as long as Result never implements IEnumerable.
There's no equivalent in LINQ to the Tap() function. List itself does actually implement a ForEach() function which is equivalent, but it's not part of the broader LINQ, and isn't an appropriate name for a non-collection type. I would probably rename it Process(), but that's a subjective choice.
TryCatch() is basically Bind/Select with exception handling. The bind and func function parameters in Bind() and TryCatch() are basically the same thing, except one returns TOut and the other returns Result. You can adjust so that they both use the same signature, and then the entire thing collapses into a single function. I'd merge them together.
And then Match() just splits the OK and Error results apart to generate the final return value. That's fine as is.
How do you handle if you need a variable from two previous functions? Say that in the fourth call, you need a value from the second? If you understand what i mean.
Check out the entire ROP playlist, I explained it
Great video! Could you cover generic exception handling in the repository pattern in clean architecture without using the Result type? I'm keen to learn how to handle exceptions cleanly in .NET applications. if you have already video then share the link here. Thanks in advance!
Great suggestion!
I wonder if that is a production ready code or a well known approach. We are using Hellang Middleware for handling web requests, that way we can return error codes from the Application Layer to the Web layer and then return a formatted error response. To me this looks like a lot of work to put together and the benefits are quite small.
I wouldn't say this is near production-ready. More showcasing an example.
Awesome. Is there a way to globally detect and handle result failure in the asp net middleware?
Yes, you could come up with something. I used to do it with a MediatR pipeline behavior
I'll do this only if there are no interns in my company. But then again I rather use F#
I think it should be a team-level agreement on if you'd use this. I'm not saying force this down everyone's throat. I find it a very interesting alternative to the "traditional" imperative paradigm.
ROP is so much cleaner in F#.
use f# to do what exactly
Yeah good luck switching to F#… I’d like too see how your management takes it
There may be interns in your company in the future.
I loved programming like this in Angular with RxJs, but it feels weird in C#, can't explain it better, maybe it's my brain is constantly going "this goes against every uni book you read".
Would be interesting to see performance metrics for the original method vs ROP. Allocations don't matter as much to me as performance.
Also would be interesting comparing the same implementation in F#, I could never fully get into it.
Yes, this is very similar to RxJs so it felt pretty natural to me. There's also Rx.NET 😁
I'm wandering how this would look if the db and related services functions would have proper async/await implemenations.
And would need to be awaited before proceeding to next function in the pipeline.
The function chains would be the same, you'd just have one await at the front, so:
await ValidateLineItems()
.Bind(...)
.TryCatch(...)
.Tap()
.Tap()
.Match();
The trick is just to create the async overloads for each method, that can accept and return a Task
@@MilanJovanovicTech debugging will be hell)
I like the result type but converting everything into extension methods makes this very unreadable and hard to debug.
Looks like we might be getting a Result type in C# soon :)
I prefer the follow errorhandling styles (in order):
1. Result pattern
2. obj | err tuple return values (golang style)
3. exceptions (but I try to avoid it like a cat avoids water)
The problem with exceptions is that they can happen anywhere down the path and they may, or may not be handled. If you don't handle them, they will explode higher up the call chain and then good friggin luck catching them all like these stupid pokemons. If you use solution 1 or 2, you force yourself handling the error immediately.
Once you've gone Result pattern, you'll never go back.
PS: The explanation with the drawbacks in the end was awesome as hell! Much appreciated!
Golang is trash.
Glad to see some fans of ROP here 😁👌
Can I get your courses from my country (Iran)?
No idea
I understand this completely. But maybe I am just to simple minded this seems like deeply excessive over engineering to solve a very simple problem that is very humans readable that pretty much any c# dev can eyeball in 10 seconds and figure exactly what it’s doing.
Well, I laid out the pros and cons the way I see them. So everyone can decide what works for them 😁
too complicated.
If you say so
@@MilanJovanovicTech it contsins an embryo to agood idea but I feel that it is too invasive.
Ah, not that complicated… it’s like the builder pattern to the next level. It’s fine and looks pretty cool.
@@icewolf1911 "its fine and it looks cool". yes I agree its cool and that is what makes it unreadable for most programmers.
@@matswessling6600 Sounds like a skills issue then. That is pretty readable, but I've been doing this for two decades now.
I get it, it looks like functional programming but why use C# instead of F# if you end up doing C# like this?
Because I can still use C# and the ecosystem I know well
@@MilanJovanovicTech true! Thanks for the video anyway! Nice to see new styles like this
I really dislike this style of programming (in c#)
Just use a global execration handler that is smart enough to know what to return to the customer.
Move validation into some global handlers too.
Just implement your api like a happy path (mostly).
Yeah I hear you 😅 I am flaming this style in the comments hahaha
Do you also dislike LINQ? 😁
Of course I expected some heat around this topic. As was the case when I previously discussed it. What's your stance on functional programming in C# (in general)?
100% agreed. Being imperative is so much better when your code can branch (e.g. exceptions can happen). You can actually step through it without the control flow bouncing all over the place due to some declarative magic logic and without having to put breakpoints inside callbacks (ugh). I use LINQ all the time, but only if there's no branching, i.e. there are no junctions in my railroad, e.g. when projecting a data structure or when querying with EF.
Exceptions and occasional result objects ftw!
@@MilanJovanovicTech I do like linq a lot.
I think the main difference with linq is that its domain is well defined.
That is set oriented selection methods.
ROP seems to be all over the place. I don’t like to tie everything together into one long fluent syntax.
Reads fine now, but how about 3 months from now?
Also it is not immediately obvious where to put or add new functionality opposed to simpler transaction script style.
FP in c# seems fun at start or in a green field project, but maintainers are going to hate you for it.
You might even curse your younger self because he wrote some FP code that barely fitted the bill but he went ahead with it anyway because it looked cool. But 3 months from now it looks like a piece of mud … 😅
This will very quickly lead to highly coupled code, with business logic sprinkled all over the place.
The Golang way 😂😂😂
Kinda
This is quite an old technique.
It is
@MilanJovanovicTech what if email_service or payment_service is async? some example?
We would add overloads that use Task
I recently joined a team where a pipeline like this was used. The problem was , the whole "old" team had left, so everyone in the team is new. This means that nobody knows what "Bind" or "Tap" means any more, and there is no documentation. This makes this code, very frustrating to maintain. We are in the process of replacing this with simpler "normal" program flow.
I like the idea of this, but there are big danger flags around it, that makes me avoid it like the plague.
The issue is your team doesn't have the competence to be engineers.
Well, they're all simple functions. Tap = no side effect. Bind = could fail. Map = like LINQ Select. What made this hard to figure out?
@@MilanJovanovicTech I gave those as examples, they are not hard to figure out, but they are not descriptive either. Tap does not tell you what it does. But again, I just used your example, in our case they were equally weirdly named, plus were then highly generic. It makes it difficult to debug, and difficult to parse. People may say it's a bad team, but I would counter that with code should be easy to parse on reading.
They TryCatch is a great example, I see that method, I know exactly what it does and what it is intended for. I see Tap, I have no clue what that means, do I really have to go delve into the methods to figure out what it does. Yes, your methods are simple, ours were not. Again, I'm not attacking anything that was presented, just my experience with these pipelines.
@TheScriptPunk Your opinion suggests a punk that no one would like to work with. Firstly, if the team can handle rewriting such code and then dealing with business tasks, they are competent enough to do their job. Secondly, the code was done in some niche way (in the C# world) and it is proven by many that it is hard to follow (we write code for people, not for our ego). Thirdly, this code does not solve anything.
I use the functional approach where it solves the problem, so I use 25% functional and 75% OOP. This was using some functional approach just to use a functional approach- not to solve the problem in a much simpler way. The only good thing from this video is that viewers can familiarize themselves with the functional approach, which is not the default way in C#.
@@Eltin123456 sure ok.
Still has nothing to do with oop
I would avoid exceptions in some places only if I have to write high performant code or in some cases where it is really needed or more appropriate than throwing an exception. Just avoiding exceptions without any significant reason doesn't make code better at all.
So, you just put "throw" in your code rather than passing data?
totally agreed. Checking result is so 1970. it makes your codebase bloat. There is a reason why .net introduced exception as an alternative. I perfer to use exception for readability then optimise to use result pattern in hot paths
How about avoiding exceptions because you know what the failure is, and you know how to handle it? (If you're thinking that's not exceptional, maybe we're onto something here).
@@MilanJovanovicTech Right, if I know how to handle some situations in code I won't throw an exception of course. Exceptions is a good tool in right hands and it is only for exception situations. If you take a look at GO lang code, you can see, in some libraries, code turns into if-hell, instead of have one place to handling errors. I don't want to say that we should use exceptions whenever where possible, it is just about they are really helpful and make your code much cleaner, if use them right.
It's like talking about "goto", many people hate it, but it is also really helpful if it is used right and it is even used in some places in .Net to improve performance, instead of creating additional overhead with flag variables, etc.
I'd rather implement Result with Service and handle everything in service. looks too complicated for me. coz need to introduce so many functions (btw no need to add custom methods in LINQ, they comes built in)
You can write the functions "in place" also. This was just my way of structuring it to lead into the solution.
Testability in those functions 100% absolute win when working with devs that are lazy. You can go behind and write the tests for them.
Interesting
If anything
I think this is great, but C# is too verbose, it's a lot nicer in F#.
Lemme guess, you're a sr dev that should actually be starting as mid level instead, and don't actually code outside of work because you got your foot in the door?
I agree that this looks better in F#, since it's a functional language. But I also like to stay in C# for other reasons.
Have you worked on event sourcing pattern ? I found it most complex pattern.
Event sourcing is fundamentally simple. It's the "maintaining an ES system" that's the hard part
Them: I don't like this approach
You: might be a matter of unfamiliarity
Them: let me find reasons having nothing to do with the approach to justify unfamiliarity.
😂
😂
I've heard these arguments in the past when talking about ROP 😅 So I expected as much. But still, I think it's worth talking about "different" ways to do things. Someone out there might see this, and it could really help them.
it seems you reinvented 'monad'))
Not reinvented - this is IT
Please, just stop doing things in C# non idiomatically. The problem with Option/Result monads in C#, is that you just don't have the support from language, to use them without a pain (like in Rust or F#). And the code becames just insanely convoluted.
If you want to express Option in C#, you can use NRT. If you need to do another way of error handling, just use exceptions.
| you just don't have the support from language
So we add it with some extension methods?
@@MilanJovanovicTech that's not the point. The point is that it is not idiomatic and it's not providing that much benefit. You basically need to pattern match every result/option, regardles if you need just to return the failed result and handle happy path (which is like 95%+ of the cases when you're using these monads).
You also can use panic in go for exception-like error handling. But should you?
@@ilushkinz4424 Some confusion here about expected and unexpected exceptions. This approach allows you to handle the expected cleanly using the capabilities that C# currently has. The readability, comes with experience, allows the developers to focus on the business rather than the infrastructure.
Idiomatic C# is anyway heading in this direction with the upcoming Discriminated Unions with Option and Result monads. So doesn't hurt to familiarize yourself with the concept.
Horrible. Mixing function and OO causes more problem and most of the times devs bringing it to the code base are literally trying to resolve problems that doesn’t exist.
Makes your conditionals actually testable
Did you ever try this approach in practice and you're speaking from experience?
I have mixed OO with functional, did not face any issues, except in the beginning, but I chalk that upto the learning curve.
@@arunnair7584 you've been using functional the whole time.
Oo is when you encapsulate data in an object.
Flow of control, var assignment, property accessing is not oo.
@@TheScriptPunk Nope. I have been using OO since 1999. Functional since around 2018 or so. I avoid getters and setters in OO too.