Πώς στήνουμε σωστά τα validations και γιατί δεν πρέπει να βάζουμε business logic στους validators
Σε πολλές εφαρμογές που χρησιμοποιούν Clean Architecture μαζί με MediatR και FluentValidation, εμφανίζεται συχνά ένα συγκεκριμένο αρχιτεκτονικό λάθος. Οι validators αρχίζουν να περιέχουν μεγάλο μέρος της επιχειρησιακής λογικής της εφαρμογής, γεμίζουν με if συνθήκες, permission checks ή ακόμη και κλήσεις σε services και repositories.
Αν και αρχικά μπορεί να φαίνεται πρακτικό, αυτό οδηγεί σε κακή δομή κώδικα και σε προβλήματα συντήρησης. Για να αποφευχθεί αυτό, πρέπει να κατανοήσουμε πρώτα τον πραγματικό ρόλο του validation μέσα σε μια Clean Architecture.
Ο ρόλος του validation στην Clean Architecture
Η λογική ελέγχου δεδομένων σε μια εφαρμογή δεν είναι μία ενιαία έννοια. Στην πράξη χωρίζεται σε τρία επίπεδα.
Το πρώτο επίπεδο αφορά το validation της μορφής του request. Το δεύτερο επίπεδο αφορά τους επιχειρησιακούς κανόνες της εφαρμογής. Το τρίτο επίπεδο αφορά τους κανόνες ακεραιότητας του domain model.
Κάθε επίπεδο πρέπει να βρίσκεται σε διαφορετικό σημείο της αρχιτεκτονικής ώστε να διατηρείται ο διαχωρισμός ευθυνών.
Input validation με FluentValidation
Το FluentValidation χρησιμοποιείται για τον έλεγχο της δομής των δεδομένων που φτάνουν στο σύστημα. Ο στόχος του είναι να διασφαλίσει ότι το request έχει σωστή μορφή πριν εκτελεστεί οποιαδήποτε επιχειρησιακή λογική.
Σε αυτό το επίπεδο ελέγχονται πράγματα όπως αν ένα πεδίο είναι υποχρεωτικό, αν ένας αριθμός βρίσκεται μέσα σε επιτρεπτά όρια ή αν μια συλλογή δεν είναι άδεια.
Ένας τυπικός validator μπορεί να μοιάζει με τον παρακάτω:
public class SaveCartItemCommandValidator
: AbstractValidator<SaveCartItemCommand>
{
public SaveCartItemCommandValidator()
{
RuleFor(x => x.EmployeeId)
.NotEmpty();
RuleFor(x => x.Actions)
.NotEmpty();
RuleForEach(x => x.Actions)
.ChildRules(action =>
{
action.RuleFor(x => x.ItemId)
.NotEmpty();
action.RuleFor(x => x.Quantity)
.GreaterThan(0);
});
}
}
Ο validator εδώ ελέγχει μόνο τη δομή του request. Δεν γνωρίζει τίποτα για permissions, business rules ή κατάσταση της βάσης δεδομένων.
Application validation και business rules
Αφού περάσει το input validation, η εφαρμογή προχωρά στον έλεγχο των επιχειρησιακών κανόνων. Αυτή η λογική ανήκει στο Application Layer, συνήθως μέσα στον handler ή σε services που καλεί ο handler.
Σε αυτό το επίπεδο ελέγχονται πράγματα όπως αν ένας εργαζόμενος έχει δικαίωμα να εκτελέσει μια ενέργεια ή αν ένα αντικείμενο μπορεί να ανατεθεί σε αυτόν.
Ένα παράδειγμα τέτοιου ελέγχου είναι το ακόλουθο:
var employee = await employeeService.GetEmployeeAsync(request.EmployeeId);
if (!employee.CanReceiveItems())
{
return Result.Fail("Employee cannot receive items");
}
Αυτή η λογική δεν έχει καμία θέση σε έναν FluentValidator γιατί δεν αφορά τη μορφή του request αλλά τη λειτουργία της εφαρμογής.
Domain validation
Το τρίτο επίπεδο βρίσκεται στο Domain Layer. Εκεί βρίσκονται οι κανόνες που προστατεύουν την ακεραιότητα των entities του συστήματος.
Για παράδειγμα, ένας εργαζόμενος που έχει αποχωρήσει από την εταιρεία δεν θα πρέπει να μπορεί να λάβει νέα αντικείμενα. Αυτή η λογική ανήκει στο domain model.
public void AssignItem(Item item)
{
if(IsTerminated)
throw new DomainException("Employee is inactive");
_items.Add(item);
}
Ακόμα και αν η εφαρμογή παραλείψει κάποιον έλεγχο στο application layer, το domain model εξακολουθεί να προστατεύει την κατάσταση του συστήματος.
Γιατί δεν πρέπει να βάζουμε business logic στους validators
Όταν ένας validator αρχίζει να περιέχει επιχειρησιακή λογική, παραβιάζεται ο βασικός κανόνας της Clean Architecture: κάθε component πρέπει να έχει μια σαφή ευθύνη.
Ο validator πρέπει να γνωρίζει μόνο τη μορφή του request. Αν αρχίσει να καλεί services, repositories ή να ελέγχει permissions, τότε αποκτά εξαρτήσεις που δεν θα έπρεπε να έχει. Αυτό οδηγεί σε coupling ανάμεσα στα layers και κάνει την αρχιτεκτονική πιο δύσκολη στη συντήρηση.
Ένα συνηθισμένο παράδειγμα λάθους είναι το εξής:
RuleFor(x => x)
.MustAsync(async (request, ct) =>
{
var employee = await employeeService.GetEmployeeAsync(request.EmployeeId);
if(employee.IsTerminated)
return false;
if(employee.Role == "Manager")
return true;
return false;
});
Σε αυτή την περίπτωση ο validator αρχίζει να εκτελεί business logic που κανονικά θα έπρεπε να βρίσκεται στον handler ή σε κάποιο application service.
Το αποτέλεσμα είναι ότι η λογική της εφαρμογής διασκορπίζεται σε διαφορετικά σημεία του κώδικα και γίνεται δύσκολο να εντοπιστεί.
Πώς διαχωρίζουμε σωστά τα validations
Η βασική αρχή είναι απλή. Το FluentValidation ελέγχει μόνο αν το request είναι σωστά διαμορφωμένο. Οι επιχειρησιακοί κανόνες εφαρμόζονται στο application layer, ενώ οι κανόνες ακεραιότητας προστατεύονται από το domain model.
Με αυτό τον διαχωρισμό κάθε κομμάτι της εφαρμογής έχει ξεκάθαρη ευθύνη. Οι validators παραμένουν μικροί και ευανάγνωστοι, οι handlers περιέχουν την επιχειρησιακή λογική και το domain model προστατεύει την κατάσταση των entities.
Έτσι η εφαρμογή παραμένει ευέλικτη, εύκολη στη συντήρηση και ευκολότερη στον έλεγχο μέσω tests.
Συμπέρασμα
Το FluentValidation είναι ένα εξαιρετικό εργαλείο για τον έλεγχο της μορφής των requests, αλλά δεν πρέπει να χρησιμοποιείται για επιχειρησιακή λογική. Όταν οι validators γεμίζουν με if συνθήκες, database calls ή permission checks, αυτό είναι συνήθως ένδειξη ότι η αρχιτεκτονική έχει αρχίσει να εκτρέπεται από τις αρχές της Clean Architecture.
Η σωστή προσέγγιση είναι να κρατάμε το FluentValidation για input validation, να τοποθετούμε τους επιχειρησιακούς κανόνες στο application layer και να αφήνουμε το domain model να προστατεύει την ακεραιότητα των entities.




Top comments (0)