Senior
Πώς λύνετε το πρόβλημα της χρήσης εξαρτήσεων των περιφερειακών συστατικών στον πυρήνα;
sobes.tech AI
Απάντηση από AI
Η επίλυση του προβλήματος χρήσης των εξαρτήσεων των περιφερειακών συστατικών (ανταπτόρων, διεπαφών) στον πυρήνα (λογική τομέα) σε μια αρχιτεκτονική, όπως η Καθαρή Αρχιτεκτονική ή το DDD, μπορεί να επιτευχθεί με την αρχή Αντιστροφής Εξαρτήσεων (Dependency Inversion Principle - DIP).
Η ουσία του DIP:
- Τα ανώτερα επίπεδα modules δεν πρέπει να εξαρτώνται από τα κατώτερα επίπεδα. Και τα δύο πρέπει να εξαρτώνται από αφαιρέσεις.
- Οι αφαιρέσεις δεν πρέπει να εξαρτώνται από λεπτομέρειες. Οι λεπτομέρειες πρέπει να εξαρτώνται από τις αφαιρέσεις.
Πρακτική εφαρμογή:
- Ο πυρήνας δηλώνει διεπαφές: Ο πυρήνας ορίζει αφαιρέσεις (διεπαφές ή αφηρημένες κλάσεις) που περιγράφουν τις απαραίτητες λειτουργίες από τα περιφερειακά συστατικά.
- Τα περιφερειακά συστατικά υλοποιούν τις διεπαφές: Εξωτερικά modules (ανταπτόρες για βάσεις δεδομένων, HTTP clients, συστήματα αρχείων κ.λπ.) υλοποιούν αυτές τις διεπαφές.
- Εισαγωγή εξαρτήσεων (Dependency Injection - DI): Τα περιφερειακά συστατικά εισάγονται στον πυρήνα από έξω, συνήθως σε υψηλότερο επίπεδο της εφαρμογής (π.χ., στον πυρήνα σύνθεσης ή με τη χρήση DI-container). Ο πυρήνας λειτουργεί μόνο με αφαιρέσεις.
Παράδειγμα χρήσης DIP σε Node.js:
Ας υποθέσουμε ότι έχουμε έναν πυρήνα που χρειάζεται πρόσβαση σε δεδομένα.
// src/domain/interfaces/UserRepository.ts
// Ο πυρήνας δηλώνει διεπαφή
export interface UserRepository {
getUserById(id: string): Promise<User | null>;
}
// src/domain/entities/User.ts
// Οντότητα πυρήνα
export class User {
constructor(public id: string, public name: string) {}
}
// src/domain/usecases/GetUserUseCase.ts
// Η χρήση πυρήνα εξαρτάται από την αφαιρέση UserRepository
import { UserRepository } from '../interfaces/UserRepository';
import { User } from '../entities/User';
export class GetUserUseCase {
constructor(private userRepository: UserRepository) {}
async execute(id: string): Promise<User | null> {
return this.userRepository.getUserById(id);
}
}
// src/infrastructure/database/MongoDbUserRepository.ts
// Περιφερειακό συστατικό που υλοποιεί την διεπαφή του πυρήνα
import { UserRepository } from '../../domain/interfaces/UserRepository';
import { User } from '../../domain/entities/User';
export class MongoDbUserRepository implements UserRepository {
// Εδώ θα είναι η λογική επικοινωνίας με το MongoDB
async getUserById(id: string): Promise<User | null> {
console.log(`Ανάκτηση χρήστη με id ${id} από MongoDB...`);
// Μοντέλο προσομοίωσης λήψης δεδομένων
if (id === '123') {
return new User('123', 'John Doe');
}
return null;
}
}
// src/app/CompositionRoot.ts
// Ο σύνθετος ρίζα συγκεντρώνει τις εξαρτήσεις
import { GetUserUseCase } from '../domain/usecases/GetUserUseCase';
import { MongoDbUserRepository } from '../infrastructure/database/MongoDbUserRepository';
// Εισαγωγή εξαρτήσεων: Ο πυρήνας λαμβάνει την υλοποίηση MongoDbUserRepository μέσω της διεπαφής
const userRepository = new MongoDbUserRepository();
const getUserUseCase = new GetUserUseCase(userRepository);
// Τώρα μπορούμε να χρησιμοποιήσουμε την περίπτωση χρήσης
async function run() {
const user = await getUserUseCase.execute('123');
if (user) {
console.log(`Βρέθηκε χρήστης: ${user.name}`);
} else {
console.log('Χρήστης δεν βρέθηκε');
}
}
run();
Πλεονεκτήματα αυτής της προσέγγισης:
- Ανεξαρτησία πυρήνα: Ο πυρήνας δεν γνωρίζει για τις συγκεκριμένες υλοποιήσεις των περιφερειακών συστατικών.
- Δοκιμαστικότητα: Ο πυρήνας είναι εύκολο να δοκιμαστεί, αντικαθιστώντας τις mock-υλοποιήσεις διεπαφών.
- Ευελιξία: Μπορεί εύκολα να αντικαταστήσει μια υλοποίηση (π.χ., βάση δεδομένων) με άλλη, χωρίς να αλλάξει ο κώδικας του πυρήνα.
- Υποστήριξη: Απλοποιείται η συντήρηση και η τροποποίηση του κώδικα.