C
C#7mo ago
Bluestopher

❔ Business Logic Validation in Service Layer

Hey all, I’m working on validating my domain objects that are passed from my framework and drivers (controllers, gRPC service, ect) in my service layer. I’m not sure what the best approach is to apply business logic on my domain entity and have chose to pickup fluentvalidation. This feels a bit overkill, but I’m not sure on what other approaches people use currently. I do not want to throw an exception for business logic validation since it is quite expensive and I would rather return a detailed message that valid failed with a list of errors for all of the validations that failed. Does anyone have any recommendations on patterns for performing validation in the service layer so I can return detailed error responses back to the controller without throwing an exception? I heard about return errors apart of a task, but I have not seen any examples of how this is accomplished.
25 Replies
Bluestopher
Bluestopher7mo ago
This approach looks good for what I am trying to accomplish https://youtu.be/a1ye9eGTB98?si=G63iYJvPRzOqt1t_. I’m just not sure if this is considered normal.
Nick Chapsas
YouTube
Don't throw exceptions in C#. Do this instead
Check out my new "Integration testing in ASP .NET Core" course and use code INTESTING1 for 20% off (first 300): https://dometrain.com/course/from-zero-to-hero-integration-testing-in-asp-net-core/ Become a Patreon and get source code access: https://www.patreon.com/nickchapsas Hello everybody I'm Nick and in this video I will show you how you ca...
JakenVeina
JakenVeina7mo ago
public interface IOperationError
{
string Code { get; }
string Message { get; }
}

public struct OperationResult
{
public bool IsSuccess { get; }
public IOperationError Error { get; }
}

public struct OperationResult<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public IOperationError Error { get; }
}
public interface IOperationError
{
string Code { get; }
string Message { get; }
}

public struct OperationResult
{
public bool IsSuccess { get; }
public IOperationError Error { get; }
}

public struct OperationResult<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public IOperationError Error { get; }
}
this is my go-to, roughly
Bluestopher
Bluestopher7mo ago
@V.EINA Jaken Nice, this is solid. But, what if you have an operation error like a validation error. The validation error could possibly contain many error messages.
JakenVeina
JakenVeina7mo ago
then write an error class to represent that that's why .Error on both of those is just IOperationError you can put whatever the hell object you want in there, as long as it implements that interface it's tedious getting errors to bubble up the way an exception would, but you can get around that a little bit with action filters like, for validation
Bluestopher
Bluestopher7mo ago
I see what you are saying.
public interface IOperationError
{
string Code { get; }
List<string> ErrorMessages { get; }
}

public struct OperationResult
{
public bool IsSuccess { get; }
public IOperationError Error { get; }
}

public struct OperationResult<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public IOperationError Error { get; }
}
public interface IOperationError
{
string Code { get; }
List<string> ErrorMessages { get; }
}

public struct OperationResult
{
public bool IsSuccess { get; }
public IOperationError Error { get; }
}

public struct OperationResult<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public IOperationError Error { get; }
}
JakenVeina
JakenVeina7mo ago
and you're one JsonConvert.Serialize() call away from having detailed error info you can return from an API yeah, no, just Message at least for me
Bluestopher
Bluestopher7mo ago
I like this pattern. Are you saying
List<string> Message { get; }
List<string> Message { get; }
?
JakenVeina
JakenVeina7mo ago
and arguably, you shouldn't even do that I'm saying more like....
Bluestopher
Bluestopher7mo ago
What would you suggest for that scenario?
JakenVeina
JakenVeina7mo ago
public class CompositeError
: IOperationError
{
public string Code
=> GetType().Name;

public IReadOnlyList<IOperationError> Errors { get; }
}
public class CompositeError
: IOperationError
{
public string Code
=> GetType().Name;

public IReadOnlyList<IOperationError> Errors { get; }
}
if you want all the errors displayed at once, it's up to your UI to interpret the error and do that, when appropriate
Bluestopher
Bluestopher7mo ago
That makes since. This is the type of error my consumers like my controller can go through as well to check the code.
JakenVeina
JakenVeina7mo ago
up to you whether you think a plain Message property is appropriate maybe it is, for PROD environments like, only include the FULL detail in DVLP
Bluestopher
Bluestopher7mo ago
This works well because it can be used by the UI and service. For example, my requests flow goes controller -> service (business logic) -> DAL. The business logic layer would be returning this error. My controller could see the error code and know to just return the error mesages and map it to a specific http error code.
JakenVeina
JakenVeina7mo ago
exactly
Bluestopher
Bluestopher7mo ago
I'm excited to get this implemented tonight since I get to work on a new service where we dont have a pattern for this yet.
JakenVeina
JakenVeina7mo ago
I would recommend looking for existing implementations like this on GitHub first I don't know of any off-hand, but I'm sure they're out there
Bluestopher
Bluestopher7mo ago
You have any recommendations? Or the name of this pattern I could use to lookup repos?
JakenVeina
JakenVeina7mo ago
look for something like "on rails" that's kinda the pattern here actually, I do know of one
JakenVeina
JakenVeina7mo ago
GitHub
GitHub - Remora/Remora.Results: A simple, versatile algebraic data ...
A simple, versatile algebraic data type for C#. Contribute to Remora/Remora.Results development by creating an account on GitHub.
Bluestopher
Bluestopher7mo ago
Sweet thanks, I'm going to dive into it more.
JakenVeina
JakenVeina7mo ago
you're still free to implement yourself, but there's value to be had in looking to others for inspiration
Bluestopher
Bluestopher7mo ago
Yeah, I agree. I don't know if I need to go as far as some of this, but the way errors are created is a nice pattern than can be added to overtime.
JakenVeina
JakenVeina7mo ago
keep in mind, this does not mean (IMO) that you should not use exceptions this should be your pattern for expected errors or normal operation errors
Bluestopher
Bluestopher7mo ago
Yeah, these are for known things like validation. If there are execptions those will be caught in a try catch. That is, a database throws an unexpected exception. We let it propogate up and log.
Accord
Accord7mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.