Replies: 11 comments
-
@Volkmire let me try to address some of these questions.
When your foundation service throws an If you decide to make one generic Now, with that being said, if you are building an orchestration level service or maybe even something higher like a coordination service. You don't really continue to wrap every exception coming from underneath or lower stream services with a new exception. You unwrap the categorization exception like The unwrapping of the existing categorization exception will help your controller remove the extra layer of categorization (if needed) and handle the specific exception you are working with. So it's not really a Russian doll more like a Shawarma wrap :-) Read more here about unwraping and rewraping for localized exceptions. There are situations then where your catch (StudentCoordinationDependencyValidationException studentCoordinationDependencyValidationException)
(when studentValidationException.InnerException
is NotFoundStudentException
or NotFoundLibraryCardException
or NotFoundStudentContactException)
{
return NotFound(studentValidationException.InnerException)
} Now, the other point you made was about the controller being tied to your Foundation service. This is partially true. Your controller doesn't really know about your Foundation service contract, but it knows about the exception model coming from downstream services. controllers (just like brokers) are not fully abstract. They know a tiny bit about your lower dependency and it's immediate dependencies so they can communicate the right message. Controllers will carry a bit of details from your local models and then convert them back into external model just how they started (being external). And that's okay because controllers aren't meant to be fully abstract, but rather disposable exposers to your business logic. They should know combine some knowledge about inner logic and outer protocols just like your Foundation services know a little about your external models and inner logic. Now, if we rely only on the stack trace that was thrown from the original exception, we lose the ability to handle these exceptions in upstream services. Imagine a situation where you have a Coordination level service. This coordination service may need to apply some retry logic or any form of exception handling at it's level - in this case here your coordination service can't deal with just orchestration services (it's dependencies) exceptions - now it has to know about all the possible 9 Foundation services that live under a maximum of 3 orchestration services underneath. Although that the Coordination service only cares about just handling any dependency exception that may occur and retry with a different route/logic to resolve an issue. I will try to address the rest of your questions as time allows. I hope this helps. |
Beta Was this translation helpful? Give feedback.
-
@hassanhabib , thanks for clarifications and commitment. :) When writing code, I'm trying to be meaningful in what I do, but the land of software design is very confusing for the inexperienced. So thanks for helping out. :) Anyway, back the point. Your answer clarified most of my concerns on that subject, but I still have some concerns left, if you wouldn't mind.
While writing this, I had a think on how would one solve this issues and had a naive idea that most of them could be solved by marker interfaces. Reason being is that categories and wrappers don't seem to have innate value (at least I'm not yet onto it) outside of categorizing exception groups for recovery behavior. But behavior is better contained by interfaces, so why not have something like this:
Then we can ditch all of the wrappers and rewraping and use pattern matching like so:
(although, I'm not sure if Polly would agree with this approach) Now, I'm probably missing some other benefit that outweighs mentioned downsides (or maybe they are not downsides?), would be glad to hear your thoughts. |
Beta Was this translation helpful? Give feedback.
-
Hi @Volkmire
This is a strong point. you are saying that upstream services are now tightly coupled with concrete implementations for the exceptions from downstream services. However, because of the strict naming conventions for these dependencies, whatever is implementing If I decide today to change the implementation of some The trick here is in the localized exceptions. You have the freedom to change the inner exception into whatever fits your new implementation. because the inner exceptions are case-specific. things like The bottom line here, is that the categorical exceptions at every layer are just as contractual as the interface itself. whatever comes underneath that is your specific implementation that is subject to change at any time without breaking your upstream services except in very rare occasions. You have to also consider that the code here is just a reflection of a thought process. If an orchestration service can call Foundations or Processings services, I can tell which dependency it's calling from the naming of the methods on these dependencies. For instance, if an orchestration service is calling
That's true. you have to implement a lot of exceptions for each service. But it's necessary for three reasons:
The unwrapping then wrapping process allows your controller to deal only with 3 types of exceptions in addition to the hybrid. Look at this example here:
I had a couple of more thoughts but I need to step out. will get back to you on the rest later. |
Beta Was this translation helpful? Give feedback.
-
@Volkmire good point. Categorical exceptions are just carriers of multiple localized more issue-focused exceptions. They make things easy if and only if you choose to operate at the categorical level. But they don't deny you access to information if you choose to operate on a more issue-based level. More often than ever, you will end up retrying on a categorical level exception that an issue-focused localized exception. But that certainly isn't a measure nor a reason for driving this level of implementation. Categorical exceptions also help Orchestration services aggregate common issues with common categories. Imagine if you have It even makes it easier to handle all of this at the controller (exposure) layer because now your controller has to deal only with one exception instead of 20. I shall come back to this thread again as time allows. |
Beta Was this translation helpful? Give feedback.
-
I'm thinking DRY here. What would happen if I forgo creating 4 wrappers per type, but instead use 4 types in total? What do I lose? Example. On a higher level (say Orchestration), if i want to retry any particular dependency call - I will be forced to wrap EXACTLY that call in a retry policy. Consider this: class Orchestrator
{
public OrchestrationResult Orchestrate()
{
return Do();
}
private OrchestrationResult Do()
{
var one = _studentService.Work();
var two = _libraryService.Work();
return new OrchestrationResult(one, two);
}
} If i apply a retry policy around the public OrchestrationResult Orchestrate()
{
return RetryOnException(Do);
} If, in this case, I would want to retry only on LibraryDependencyValidationException, I will be forced to call a If i want to retry any particular call, I need to wrap EXACTLY that call in a retry policy, meaning - I don't need to know what exception name being thrown, all i care is it's category/interface: private OrchestrationResult Do()
{
var one = RetryOnException(_studentService.Work);
var two = RetryOnException(_libraryService.Work);
return new OrchestrationResult(one, two);
} I can't think of an example, where I would care that it's If we consider Am I wrong? Could you, perhaps, provide a counter example? |
Beta Was this translation helpful? Give feedback.
-
@Volkmire what if you need to retry on a certain occasion of the dependency call? Your example here implies that I retry on all errors that come from a dependency. |
Beta Was this translation helpful? Give feedback.
-
@hassanhabib , not quite got you. If I retry in an orchestration service, I don't know what dependencies does my Or do you mean a case, where I have private OrchestrationResult Do()
{
var one = RetryOnException(_studentService.Work);
var two = _libraryService.Work();
return new OrchestrationResult(one, two);
} Just in case, here's an example of RetryOnException method for a particular call, if that caused a misunderstanding: private Student RetryOnException(Func<Student> f, int retryCount = 3)
{
try
{
return f();
}
catch(StudentDepencyValidationException ex)
{
return retryCount > 0 ? RetryOnException(f, --retryCount) : throw ex;
}
} |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Consider the implementations a pseudo-code. It's just a general representation of what is happening and where it is happening.
Is there, perhaps, an example of The Standard implementation with all the bells and whistles? |
Beta Was this translation helpful? Give feedback.
-
I will try to put something together as soon as possible. might take a bit of time because I'm trying to make sure that any public representation of these practices is properly documented because it's not just you and me on the internet haha. |
Beta Was this translation helpful? Give feedback.
-
Sure thing. To be honest, considering the amount of content you are pumping out, I'm a little afraid for your sanity. :> |
Beta Was this translation helpful? Give feedback.
-
Disclaimer: i might be a potato.
Hi, @hassanhabib !
After reading a good chunk of the Standard, I sense that it has a lot of valuable knowledge, but also I have a feeling that maybe it's a little bit too... abstract? I caught myself being unsure on where would I put even the most common everyday concerns within the model.
I suspect the answers already are in some form in The Standard and my lizard brain is just unable to see or understand them, but I also have a feeling that concrete implementation of the abstraction would help a ton.
To be more specific, below I will provide examples of what I'm confused about, if someone could comment on how to use them within the Standard, it would help me a lot.
Thanks in advance. :)
Examples:
Beta Was this translation helpful? Give feedback.
All reactions