Το πρόβλημα πριν από το εργαλείο
Κάθε αρχιτεκτονική επιλογή πρέπει να απαντά σε ένα ερώτημα:
Ποιο πρόβλημα λύνει;
Ο MediatR είναι υλοποίηση του Mediator Pattern. Δεν είναι framework για database, δεν είναι μηχανισμός performance, ούτε μαγική λύση καθαρού κώδικα. Είναι μηχανισμός οργάνωσης ροής αιτημάτων.
Άρα το πρώτο φιλοσοφικό ερώτημα (με την αριστοτελική λογική του “αιτίου”) είναι:
Υπάρχει ανάγκη διαμεσολάβησης μεταξύ αποστολέα και εκτελεστή;
Αν δεν υπάρχει τέτοια ανάγκη, τότε η χρήση του είναι περιττή.
Για να κατανοήσουμε αν χρειάζεται, πρέπει πρώτα να καταλάβουμε τι ακριβώς κάνει.
Ανάλυση και αιτιολόγηση
1. Τι είναι ουσιαστικά ο MediatR;
Σε μια τυπική εφαρμογή χωρίς mediator έχουμε:
Controller → Service → Repository
Ο controller γνωρίζει το service. Το service γνωρίζει repositories. Υπάρχει άμεση εξάρτηση.
Με MediatR έχουμε:
Controller → Mediator → Handler
Ο controller δεν γνωρίζει service. Στέλνει ένα μήνυμα (Command ή Query). Κάποιος handler το αναλαμβάνει.
Η ερώτηση δεν είναι «είναι πιο καθαρό;»
Η ερώτηση είναι: Γιατί να μην καλεί απευθείας το service;
Η απάντηση είναι η εξής:
Ο mediator εισάγει ένα επίπεδο έμμεσης επικοινωνίας. Αυτό μειώνει τη σύζευξη (coupling). Όταν ο αποστολέας δεν γνωρίζει τον παραλήπτη, το σύστημα γίνεται πιο επεκτάσιμο.
Αλλά αυτό έχει κόστος: περισσότερη πολυπλοκότητα.
Άρα ήδη έχουμε μια πρώτη αρχή:
Αν η μείωση σύζευξης δεν είναι πρόβλημα στο σύστημά σου, ο mediator δεν είναι λύση.
2. Η πραγματική δύναμη: Οργάνωση use cases
Ο MediatR ενθαρρύνει αρχιτεκτονική τύπου CQRS. Κάθε επιχειρησιακή πράξη γίνεται ξεχωριστό αντικείμενο.
Παράδειγμα σκέψης:
Όταν δημιουργώ έναν εργαζόμενο, αυτό δεν είναι "μέθοδος ενός service". Είναι ένα επιχειρησιακό γεγονός. Είναι use case.
Η μετατροπή κάθε πράξης σε Command έχει φιλοσοφικό υπόβαθρο:
Αναγκάζει το σύστημα να οργανωθεί γύρω από συμπεριφορές και όχι γύρω από data tables.
Αυτό είναι πολύ σημαντικό σε μεγάλα συστήματα.
Σε μικρά όμως;
Αν έχεις 8 endpoints και απλή λογική CRUD, τότε απλώς μετατρέπεις 8 μεθόδους σε 8 κλάσεις χωρίς ουσιαστικό όφελος.
Εδώ φαίνεται η αρχή της αναλογικότητας:
Η αρχιτεκτονική πρέπει να είναι ανάλογη του προβλήματος.
3. Pipeline Behaviors
Εδώ βρίσκεται η ουσία.
Ο MediatR **επιτρέπει **behaviors, δηλαδή middleware γύρω από κάθε request:
Validation
Logging
Transaction handling
Authorization
Performance metrics
Η αξία δεν είναι ότι καλείς handler.
Η αξία είναι ότι μπορείς να πεις:
"Κάθε Command θα εκτελείται μέσα σε transaction."
Χωρίς να γράψεις transaction code σε κάθε handler.
Αυτό είναι αρχιτεκτονική καθαρότητα.
Άρα:
Αν χρησιμοποιείς MediatR χωρίς behaviors, έχεις χάσει το 60% της αξίας του.
4. Πολυπλοκότητα και γνωστικό κόστος
Κάθε abstraction έχει κόστος.
Ένας junior developer χωρίς MediatR διαβάζει:
Controller → Service → Repo
Με MediatR διαβάζει:
Controller → Command → Handler → Service → Repo
και ενδεχομένως 3 behaviors στη μέση.
Αν το project είναι μικρό, αυτή η επιπλέον αφαίρεση αυξάνει τη γνωστική επιβάρυνση.
Ο Αριστοτέλης θα έλεγε:
Δεν προσθέτουμε μορφή χωρίς αναγκαιότητα.
5. Πότε είναι πραγματικά χρήσιμος;
Είναι χρήσιμος όταν:
- Το σύστημα έχει πολλά use cases.
- Υπάρχει ανάγκη για ξεκάθαρο separation business operations.
- Θέλεις centralized cross-cutting logic.
- Η ομάδα είναι αρκετά ώριμη να διαχειριστεί την πολυπλοκότητα.
- Θέλεις να οργανώσεις το σύστημα γύρω από συμπεριφορά και όχι data access.
Δεν είναι χρήσιμος όταν:
- Το project είναι μικρό.
- Οι handlers είναι απλά passthrough.
- Δεν υπάρχουν pipeline behaviors.
- Δεν υπάρχει CQRS λογική.
- Προστέθηκε “για να μοιάζει enterprise”.
Η ουσία: Τι λύνει και τι δεν λύνει
Δεν λύνει:
- Performance
- Database consistency
- Κακό domain modeling
- Κακή κατανόηση business logic
Λύνει:
- Οργανωτική πολυπλοκότητα σε μεγάλα συστήματα
- Cross-cutting concerns με καθαρό τρόπο
- Μείωση σύζευξης μεταξύ layers
Επαναληπτικά θα λέγαμε ότι
Σε κάθε σοβαρή εφαρμογή υπάρχουν λειτουργίες που δεν ανήκουν στην επιχειρησιακή λογική, αλλά πρέπει να εκτελούνται παντού.
Για παράδειγμα:
- Έλεγχος εγκυρότητας (validation)
- Logging
- Έλεγχος δικαιωμάτων
- Διαχείριση transactions
- Μέτρηση χρόνου εκτέλεσης
- Exception handling
Αυτές οι λειτουργίες ονομάζονται cross-cutting concerns. Διατρέχουν οριζόντια το σύστημα.
Το θεμελιώδες ερώτημα είναι:
Πού πρέπει να τοποθετηθούν;
Αν τα βάλουμε μέσα σε κάθε handler, επαναλαμβάνουμε κώδικα.
Αν τα βάλουμε στους controllers, μολύνουμε το presentation layer.
Αν τα βάλουμε σε services, χάνεται η καθαρότητα της επιχειρησιακής πράξης.
Εδώ εμφανίζεται η αρχή της διάκρισης ευθυνών (Separation of Concerns).
Και εδώ ακριβώς αρχίζουν να έχουν νόημα τα Pipeline Behaviors.
Τι είναι ένα Pipeline Behavior;
Ένα pipeline behavior είναι ένας μηχανισμός που εκτελείται πριν και μετά από κάθε request του MediatR.
Λειτουργεί όπως το middleware στο ASP.NET Core.
Η ροή γίνεται:
Request
→ Behavior 1
→ Behavior 2
→ Behavior 3
→ Handler
→ Behavior 3
→ Behavior 2
→ Behavior 1
Ο handler ασχολείται μόνο με business logic.
Τα behaviors αναλαμβάνουν τα υπόλοιπα.
Γιατί αυτό είναι αρχιτεκτονικά σημαντικό;
Ας δούμε το "γιατί".
- Διαχωρισμός ευθυνών
Ο handler έχει μία ευθύνη: να υλοποιήσει ένα use case.
Δεν πρέπει να:
- ανοίγει transaction
- γράφει logs
- ελέγχει authorization
- κάνει validation
Αν τα κάνει όλα αυτά, παραβιάζει την αρχή Single Responsibility.
Το pipeline επιτρέπει καθαρό διαχωρισμό.
Συγκεντρωτικός έλεγχος
Αν αύριο αποφασίσεις ότι:
"Κάθε Command πρέπει να εκτελείται μέσα σε transaction"
Με pipeline behavior το γράφεις μία φορά.
Χωρίς pipeline:
Θα πρέπει να βεβαιωθείς ότι κάθε handler ανοίγει και κλείνει transaction σωστά.
Αυτό είναι σφάλμα σχεδιασμού, γιατί βασίζεται στην πειθαρχία προγραμματιστών.
Η αρχιτεκτονική δεν πρέπει να βασίζεται στην πειθαρχία.
Πρέπει να επιβάλλει κανόνες.
Ομοιομορφία συμπεριφοράς
Όλα τα requests περνούν από το ίδιο μονοπάτι.
Αυτό σημαίνει:
- Κάθε request καταγράφεται.
- Κάθε request μετριέται.
- Κάθε request επικυρώνεται.
- Κάθε request προστατεύεται.
Αυτό δημιουργεί προβλεψιμότητα.
Και η προβλεψιμότητα είναι βασική αρχή στα μεγάλα συστήματα.
Το ισχυρότερο παράδειγμα: Transaction Behavior
Ας το αναλύσουμε βαθιά.
Χωρίς pipeline:
Κάθε handler πρέπει να γράφει:
BeginTransaction
Execute logic
Commit
Κάποιος θα το ξεχάσει. Κάποιος θα το κάνει λάθος.
Θα προκύψουν partial commits.
Με pipeline behavior:
Ορίζεις ότι:
"Κάθε Command εκτελείται μέσα σε Unit of Work."
Άρα:
- Ανοίγει transaction πριν το handler.
- Αν ολοκληρωθεί επιτυχώς → commit.
- Αν πεταχτεί exception → rollback.
Ο handler δεν γνωρίζει τίποτα για transactions.
Αυτό είναι αρχιτεκτονική ωριμότητα.
Τι να θυμάσαι
Ο MediatR δεν είναι καλός ούτε κακός. Είναι εργαλείο.
Το σωστό ερώτημα δεν είναι:
«Είναι μοντέρνος;»
Αλλά:
«Υπάρχει στο σύστημά μου ανάγκη για διαμεσολάβηση και οργάνωση use cases;»
Αν ναι, τότε έχει λόγο ύπαρξης.
Αν όχι, τότε προσθέτει στρώματα χωρίς να προσθέτει ουσία.
Η αρχιτεκτονική δεν είναι θέμα τάσης.
Είναι θέμα αναγκαιότητας.
Όπως σε κάθε επιστημονική επιλογή, πρώτα αναλύουμε το αίτιο και μετά επιλέγουμε το μέσο.
Top comments (0)