Lage
Ich habe heute Abend eine Antwort auf eine Frage zu StackOverflow gegeben.
Die Frage:
Die Bearbeitung eines vorhandenen Objekts sollte in der Repository-Ebene oder im Service erfolgen.
Zum Beispiel, wenn ich einen Benutzer habe, der Schulden hat. Ich möchte seine Schulden ändern. Soll ich es im UserRepository oder im Service zum Beispiel BuyingService tun, indem ich ein Objekt abrufe, es bearbeite und speichere?
Meine Antwort:
Sie sollten die Verantwortung für die Mutation eines Objekts in dasselbe Objekt übernehmen und das Repository zum Abrufen dieses Objekts verwenden.
Beispielsituation:
class User {
private int debt; // debt in cents
private string name;
// getters
public void makePayment(int cents){
debt -= cents;
}
}
class UserRepository {
public User GetUserByName(string name){
// Get appropriate user from database
}
}
Ein Kommentar, den ich erhalten habe:
Geschäftslogik sollte wirklich in einem Service sein. Nicht in einem Modell.
Was sagt das Internet?
Das hat mich auf die Suche gebracht, da ich nie wirklich (bewusst) eine Service-Schicht verwendet habe. Ich habe angefangen, das Service-Layer-Muster und das Muster der Arbeitseinheit zu lesen, kann aber noch nicht sagen, dass ich davon überzeugt bin, dass ein Service-Layer verwendet werden muss.
Nehmen Sie zum Beispiel diesen Artikel von Martin Fowler über das Antimuster eines anämischen Domänenmodells:
Es gibt Objekte, von denen viele nach den Substantiven im Domänenbereich benannt sind, und diese Objekte sind mit den reichen Beziehungen und Strukturen verbunden, über die echte Domänenmodelle verfügen. Der Haken kommt, wenn man sich das Verhalten ansieht und merkt, dass es an diesen Objekten kaum ein Verhalten gibt, so dass sie kaum mehr sind als Säcke voller Getter und Setter. Tatsächlich enthalten diese Modelle häufig Entwurfsregeln, die besagen, dass den Domänenobjekten keine Domänenlogik hinzugefügt werden soll. Stattdessen gibt es eine Reihe von Dienstobjekten, die die gesamte Domänenlogik erfassen. Diese Dienste stehen über dem Domänenmodell und verwenden das Domänenmodell für Daten.
(...) Die Logik, die sich in einem Domänenobjekt befinden sollte, ist Domänenlogik - Validierungen, Berechnungen, Geschäftsregeln - wie auch immer Sie es nennen möchten.
Für mich schien das genau das zu sein, worum es in der Situation ging: Ich befürwortete die Manipulation der Daten eines Objekts, indem ich Methoden innerhalb dieser Klasse einführte, die genau das tun. Mir ist jedoch klar, dass dies so oder so gegeben sein sollte, und es hat wahrscheinlich mehr damit zu tun, wie diese Methoden aufgerufen werden (unter Verwendung eines Repositorys).
Ich hatte auch das Gefühl, dass in diesem Artikel (siehe unten) eine Serviceebene eher als Fassade betrachtet wird , die die Arbeit an das zugrunde liegende Modell delegiert, als als eine tatsächliche arbeitsintensive Ebene.
Application Layer [sein Name für Service Layer]: Definiert die Aufgaben, die die Software ausführen soll, und weist die ausdrucksstarken Domänenobjekte an, Probleme zu lösen. Die Aufgaben, für die diese Schicht zuständig ist, sind für das Unternehmen von Bedeutung oder für die Interaktion mit den Anwendungsschichten anderer Systeme erforderlich. Diese Schicht wird dünn gehalten. Es enthält keine Geschäftsregeln oder Kenntnisse, sondern koordiniert nur Aufgaben und delegiert die Arbeit an Kollaborationen von Domänenobjekten in der nächsten Ebene. Es gibt keinen Status, der die Geschäftssituation widerspiegelt, aber es gibt einen Status, der den Fortschritt einer Aufgabe für den Benutzer oder das Programm widerspiegelt.
Welche verstärkt hier :
Service-Schnittstellen. Dienste stellen eine Dienstschnittstelle bereit, an die alle eingehenden Nachrichten gesendet werden. Sie können sich eine Serviceschnittstelle als Fassade vorstellen, die die in der Anwendung implementierte Geschäftslogik (normalerweise die Logik in der Geschäftsschicht) potenziellen Verbrauchern zugänglich macht.
Und hier :
Die Serviceschicht sollte keine Anwendung oder Geschäftslogik enthalten und sich in erster Linie auf einige wenige Aspekte konzentrieren. Es sollte Business Layer-Aufrufe einschließen, Ihre Domain in eine gemeinsame Sprache übersetzen, die Ihre Kunden verstehen können, und das Kommunikationsmedium zwischen Server und anforderndem Client handhaben.
Dies ist ein schwerwiegender Gegensatz zu anderen Ressourcen , die sich mit der Service-Schicht befassen:
Die Serviceschicht sollte aus Klassen mit Methoden bestehen, die Arbeitseinheiten mit Aktionen sind, die zu derselben Transaktion gehören.
Oder die zweite Antwort auf eine Frage, die ich bereits verlinkt habe:
Irgendwann möchte Ihre Anwendung eine Geschäftslogik. Möglicherweise möchten Sie die Eingabe auch validieren, um sicherzustellen, dass keine bösen oder fehlerhaften Daten angefordert werden. Diese Logik gehört in Ihre Service-Schicht.
"Lösung"?
Nach den Richtlinien in dieser Antwort habe ich den folgenden Ansatz gefunden, bei dem eine Service-Schicht verwendet wird:
class UserController : Controller {
private UserService _userService;
public UserController(UserService userService){
_userService = userService;
}
public ActionResult MakeHimPay(string username, int amount) {
_userService.MakeHimPay(username, amount);
return RedirectToAction("ShowUserOverview");
}
public ActionResult ShowUserOverview() {
return View();
}
}
class UserService {
private IUserRepository _userRepository;
public UserService(IUserRepository userRepository) {
_userRepository = userRepository;
}
public void MakeHimPay(username, amount) {
_userRepository.GetUserByName(username).makePayment(amount);
}
}
class UserRepository {
public User GetUserByName(string name){
// Get appropriate user from database
}
}
class User {
private int debt; // debt in cents
private string name;
// getters
public void makePayment(int cents){
debt -= cents;
}
}
Fazit
Insgesamt hat sich hier nicht viel geändert: Der Code vom Controller ist in die Service-Schicht übergegangen (was eine gute Sache ist, also gibt es einen Vorteil bei diesem Ansatz). Dies scheint jedoch nichts mit meiner ursprünglichen Antwort zu tun zu haben.
Mir ist klar, dass Entwurfsmuster Richtlinien sind und keine in Stein gemeißelten Regeln, die wann immer möglich umgesetzt werden sollen. Dennoch habe ich keine definitive Erklärung für die Serviceschicht gefunden und wie sie zu betrachten ist.
Ist es ein Mittel, einfach die Logik aus dem Controller zu extrahieren und stattdessen in einen Service zu integrieren?
Soll es einen Vertrag zwischen dem Controller und der Domain bilden?
Sollte es eine Schicht zwischen der Domain und der Service-Schicht geben?
Und zu guter Letzt: Nach dem Originalkommentar
Geschäftslogik sollte wirklich in einem Service sein. Nicht in einem Modell.
Ist das richtig?
- Wie würde ich meine Geschäftslogik in einem Service anstelle des Modells einführen?