Laden...

DI/IoC in der Praxis (und korrekter Aufbau der zugehörigen Klassen)

Erstellt von haarrrgh vor 13 Jahren Letzter Beitrag vor 13 Jahren 31.695 Views
H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren
DI/IoC in der Praxis (und korrekter Aufbau der zugehörigen Klassen)

Ich habe inzwischen gefühlte 1000 Artikel zum Thema DI/IoC/Container gelesen (und ja, ich habe auch hier im Forum danach gesucht und das alles gelesen), ich habe verstanden wofür man es braucht und ich bin der Meinung daß ich es sehr gut gebrauchen kann.

Irgendwie hat es bei mir aber noch nicht so richtig "Klick" gemacht, was die Umsetzung in die Praxis betrifft.
Ich versuche mal mein Problem zu schildern:

So ziemlich alle Beispiele die ich gefunden habe sind mit Loggern, MailSendern oder dergleichen.
Also "Hilfsobjekte" die man einfach einmal mit einem parameterlosen Konstruktor erzeugen und dann benutzen kann.
Sowas hier:

public class MyClass
{
    private ILogger logger;

    public MyClass(ILogger logger)
    {
        this.logger = logger;
    }

    public void MachWas()
    {
        this.logger.Log(...);
    }
}

[...]

// ...und dann beim Start der Anwendung:
Bind<ILogger>.To<FileLogger>();

Und bei Loggern etc. habe ich dann auch schon oft Beispiele gesehen wo direkt beim Programmstart per Container.Resolve<ILogger> EINE Logger-Instanz erzeugt wird, und die wird dann während der ganzen Programmlaufzeit benutzt.

In den "fortgeschritteneren" Beispielen wird dann kein parameterloser Konstruktor benutzt wird, sondern die Objekte haben selber Abhängigkeiten.
Das sind dann aber auch wieder nur Objekte die selber einen parameterlosen Konstruktor haben, also die der Container auch einfach so aus dem Nichts erzeugen kann.

Was ich bis jetzt aber noch nicht begriffen habe:

Was mache ich, wenn ich die Objekte nicht schon beim Programmstart erzeugen, sondern sie irgendwann später anhand Benutzereingaben (z.B. Auftragsnummer) aus der Datenbank laden muß?
Sowas wird in den Beispielen nie gezeigt. Aus meiner Sicht ist das das Wichtigste, denn Logger etc. sind ja nur Beiwerk.

Ein schönes Beispiel was mir gerade in den Sinn kommt ist die Erzeugung von Aufträgen und Auftragspositionen.
Der Auftrag enthält einen Artikel und gehört zu einem Kunden (der Einfachheit halber lasse ich die Auftragsposition für dieses Beispiel jetzt mal weg).
ArtikelNr und KundenNr weiß ich vorher, also brauche ich beim Erzeugen eines neuen Auftrags irgendwoher je ein Artikel- und Kunden-Objekt deren Inhalt ich irgendwann aus der Datenbank laden muß.
Also muß ich irgendwo IArtikel und IKunde "injecten", laut dem was ich gelesen habe idealerweise in einen Konstruktor.

Und jetzt geht es los:

1. Wohin überhaupt mit der Methode die den neuen Auftrag erzeugt?

a) In die Auftragsklasse selber? (sprich, es wäre der Konstruktor)
Dann müßten Artikel und Kunde im Konstruktor des Auftrags übergeben werden...und das werden sie dann bei jeder erzeugten Instanz, auch wenn ich dann vielleicht eine andere Methode ausführen will die weder Artikel noch Kunden braucht, oder einfach nur eine Property eines bestehenden Auftrags ändern will.

b) In eine separate Klasse, "AuftragsManager" o.ä.?
Der AuftragsManager hat wahrscheinlich auch noch mehr Methoden die weder Artikel noch Kunden brauchen (z.B. Auftrag löschen), also habe ich wieder das gleiche Problem wie bei Variante a.
(Und separate Klassen à la "AuftragsErzeuger", "AuftragsLöscher" etc. um das zu vermeiden wären übertrieben, oder?)
Außerdem brauche ich im AuftragsManager dann ja auch irgendwoher ein neues (leeres) Auftrag-Objekt.
Erzeuge ich das direkt im AuftragsManager mit "new", oder muß ich das auch injecten?
Vom Gefühl her hätte ich das jetzt mit "new" gemacht (Sachen wie Logger etc. injected man da es theoretisch verschiedene Sorten von Loggern geben kann, aber ein Auftrag ist und bleibt ein Auftrag), aber irgendwo habe ich auch mal gelesen daß es ein schlechtes Zeichen wäre wenn im Code auch nur ein einziges "new" vorkommt.

2. An welcher Stelle lade ich Artikel & Kunde aus der Datenbank? Ich mache es auf jeden Fall via Repository (NHibernate), aber wo genau?

a) Zwischen der UI (wo der User auf den "Neuer Auftrag"-Button geklickt hat) und der Klasse die den Auftrag erzeugt gibt es noch eine weitere Schicht, "AuftragsService" oder sowas.
Diese Klasse bekommt KundenNr und ArtikelNr übergeben, lädt mit Hilfe von IArtikelRepository und IKundeRepository die eigentlichen Artikel- und Kunden-Objekte und übergibt sie dann der Klasse die den Auftrag erzeugt.
Die Repositories bekomme ich also vom Container, aber die Übergabe der geladenen Artikel- und Kunden-Objekte an die auftragserzeugende Klasse mache ich "zu Fuß".

b) Ich übergebe dem Konstruktor der Klasse die den Auftrag erzeugt gar nicht IArtikel und IKunde selber, sondern IArtikelRepository und IKundeRepository. Das eigentliche Laden von Artikel & Kunde passiert also schon in der Klasse selber, aber halt mit Hilfe der injecteten Repositories.
Dann muß ich der Klasse außer den Repositories selber aber auch noch ArtikelNr und KundenNr übergeben, damit die Klasse weiß was sie überhaupt aus den Repositories laden muß.
Also de facto ZWEI Konstruktorparameter pro benötigtem Objekt.

3. Woher genau kriege ich die Repositories?

a) Erzeuge ich die einmalig beim Starten des Programms und halte sie im Speicher (genau wie ich das oben schon mit dem Logger geschrieben hatte: Container.Resolve<IKundeRepository>)?
Dann sind u.U. ziemlich viele Repositories ständig im Speicher, obwohl ich sie vielleicht nur ganz selten brauche.

b) Lasse ich sie mir automatisch vom Container erzeugen, in dem Moment wo ich sie brauche?
Dann werden oft benötigte Repositories immer und immer wieder neu erzeugt.

4. Ich muß den frisch erzeugten Auftrag ja auch noch in der Datenbank speichern, ebenfalls per Repository.
Wenn die Auftragsklasse sich selbst erzeugt (siehe 1a), dann ist die Auftrag dafür verantwortlich sich selbst zu speichern. Das sollte er eigentlich nicht, oder?
Das würde bei Frage 1 eher für Antwort b) sprechen.
Außerdem, egal ob 1a) oder 1b) richtig ist, bedeutet das ja schon wieder einen weiteren Konstruktorparameter, da ich ja auch noch ein AuftragRepository übergeben muß.
Und wenn Auftrag oder AuftragsManager noch ein paar weitere Methoden haben die noch andere Objekte brauchen, dann brauche ich dafür noch mehr Konstruktorparameter. Das artet ziemlich schnell aus, oder?

Ihr seht, irgendwie fehlt mir da noch der rote Faden 🤔

Deshalb würde ich gerne mal von Leuten mit DI/IoC-Erfahrung hören wie sie das machen.
Gibt es irgendwelche Best Practices für sowas?

1.373 Beiträge seit 2004
vor 13 Jahren

Hallo,

Und bei Loggern etc. habe ich dann auch schon oft Beispiele gesehen wo direkt beim Programmstart per Container.Resolve<ILogger> EINE Logger-Instanz erzeugt wird, und die wird dann während der ganzen Programmlaufzeit benutzt.

Ein interessantes Beispiel. Idealerweise "erzeugt" man gar keine Logger auf diese Weise - der Container erzeugt die Instanz(en) selber, wenn sie als Abhängigkeit benötigt werden. Bei den typischen Loggingframeworks (NLog, log4net) erzeugt man übrigens nicht nur eine Instanz, sondern viele verschiedene, typischerweise mit dem (Namen vom) Typ, der den Logger benötigt, ausgestattet, um dann bei der Ausgabe unterscheiden zu können, von welchem Logger die Ausgabe stammt. Das heißt, das gerade das Auflösen einer Logger Instanz gar nicht so trivial ist: der neu erzeugten Instanz muss der Name oder der Typ des abhängigen Objektes mitgegeben werden. Glücklicherweise hat ein DI-Container Zugriff auf diese Information.

Und so halte ich es persönlich auch im Allgemeinen: ein Objekt, dass mit dem DI-Container erzeugt wird, sollte bei der Erzeugung nur die Informationen benötigen, die der Container sich selbst zusammensuchen kann: also andere Abhängigkeiten, vorkonfigurierte Werte und Objekte aus dem Kontext der Objekterzeugung. Auch wenn die meisten DI-Container beim Aufruf von Resolve die manuelle Übergabe von Parametern zulassen, rate ich persönlich davon ab, da meist mit Strings gearbeitet wird, um den Parametern Werte zuzuweisen, was leicht zu Laufzeitfehlern führt und schlecht zu refaktorisieren ist.

Möchte man jedoch transiente Objekte erzeugen, die sowohl zu injizierende Abhängigkeiten haben als auch Werte benötigen, die der Container sich nicht selbst suchen kann, empfehle ich eine Kombination aus Fabrik und DI-Container. Eine Fabrik wird u.a. dann eingesetzt, wenn die Erzeugung einer validen Instanz eines Objektes nicht nur aus dem Aufruf des Konstruktors besteht. Die beschriebene Situation wäre so ein Fall:


interface IArticle {
  void Initialize(string number, string name);
}

class Article : IArticle {
  public Article(ISomeService serviceViaDI){...}
}

class ArticleFactory {
  IContainer container;
  public ArticleFactory(IContainer container){
    this.container = container;
  }

  public IArticle Create(string number, string name){
    var article = container.Resolve<IArticle>();
    article.Initialize(number, name);
    return article;
  }
}

Die Factory kann man jetzt in die Klassen injizieren, die Instanzen von IArticle erstelle müssen.

Hier zeigt sich, dass ein DI-Container und eine Fabrik lediglich eine gemeinsame Schnittmenge besitzen: ein DI-Container ist nicht unbedingt eine vollständige Fabrik, da ihm wie oben beschrieben ggf. die Kenntnis fehlt, um vollständig initialisierte Objekte zu erstellen. Andererseits ist eine Fabrik keine Obermenge eines DI-Containers, da letzterer auch Lifecycle-Management übernimmt.

Es ist allerdings in diesem konkreten Beispiel natürlich fraglich, ob ich überhaupt soweit gehen möchte und Artikel über einen DI-Container resolven möchte, oder anders: sollte ein Artikel (eine Domain Entity) ggf. benötigte Services überhaupt injiziert bekommen - DDD Puristen sagen nein (inject services into domain entities), zumindest dann, wenn die Abhängigkeiten aus einer anderen logischen Schicht stammen (also z.B. aus der Infrastruktur).

In der Praxis ist es bei mir tatsächlich so, dass ich Domain-Objekte (Artikel, Kunde, Auftrag) in den meisten Fällen gar nicht über einen DI-Container erzeugen lasse, manchmal aber über eine Fabrik, da sie die physische Erzeugung (new XYZ) und die ggf. komplexe Initialisierung kapselt.

Aber vielleicht kommen wir mal konkreter zu deinen Fragen:

  1. Wohin überhaupt mit der Methode die den neuen Auftrag erzeugt?

a) In die Auftragsklasse selber? (sprich, es wäre der Konstruktor)
...
b) In eine separate Klasse, "AuftragsManager" o.ä.?

Ich weiß nicht, ob ich die Frage richtig verstanden habe, aber dein Gefühl ist mMn richtig: geh zunächst vom einfachsten Fall aus und nimm für solche Domain-Objekte new. Sollte die Erstellung eines Auftrages komplex sein, kann man sie in eine Fabrik auslagern.


var kunde = KundeAusDatenbank(„Lieschen Müller“);
var artikel = ArtikelAusDatenbank(„Banane“);

var auftrag = new Auftrag(kunde);
// Lieschen kauft 5 Bananen.
auftrag.PositionHinzufügen(artikel, 5); 

  1. An welcher Stelle lade ich Artikel & Kunde aus der Datenbank? Ich mache es auf jeden Fall via Repository (NHibernate), aber wo genau?

Das lässt sich nicht pauschal beantworten und hat mit DI eigentlich wenig zu tun.

Beispiele:
In einer einfachen MVC Anwendung kannst du das z.B. im Controller machen. Etwa in ASP.NET MVC:


class OrderController : Controller {
  // alle injiziert:
  IOrderRepository orders;
  IArticleRepository articles;
  ICustomerRepository customers;
  IUnitOfWorkProvider unitOfWorkProvider;
  
  public ActionResult PlaceOrder(string customerNr, string articleNr, int quantity){
    ICustomer customer = customers.Find(customerNr);
    IArticle article = articles.Find(articleNr);
    IOrder order = new Order(customer);
    order.AddLine(article, quantity);
    orders.Add(order);
    unitOfWorkProvider.Current.Commit();
  }
}

Um das ganze vom Controller zu lösen ist es aber ggf. sinnvoll, das in eine eigene Klasse zu schieben, also etwa in einen Servicelayer. Das erhöht Wiederverwendbarkeit und sorgt für kleinere, spezialisiertere Klassen (hilft dem Single Responsibility Principle).

In einer nachrichtenorientierten Architektur wird das im Nachrichtenhandler gemacht:


public OrderController : Controller {
  IBus bus; // injiziert

  public ActionResult PlaceOrder(string customerNr, string articleNr, int quantity){
    bus.Send(new PlaceOrderCommand(customerNr, articleNr, quantity);
  }  
}

.. 

class PlaceOrderCommandHandler : CommandHandler<PlaceOrderCommand> {
  // alle injiziert:
  IOrderRepository orders;
  IArticleRepository articles;
  ICustomerRepository customers;
  IUnitOfWorkProvider unitOfWorkProvider;
  
  public void Handle(PlaceOrderCommand cmd){
   ...
  }
}

Du siehst, es gibt kein zwingendes Patentrezept.

  1. Woher genau kriege ich die Repositories?

a) Erzeuge ich die einmalig beim Starten des Programms und halte sie im Speicher (genau wie ich das oben schon mit dem Logger geschrieben hatte: Container.Resolve<IKundeRepository>)?
Dann sind u.U. ziemlich viele Repositories ständig im Speicher, obwohl ich sie vielleicht nur ganz selten brauche.

b) Lasse ich sie mir automatisch vom Container erzeugen, in dem Moment wo ich sie brauche?
Dann werden oft benötigte Repositories immer und immer wieder neu erzeugt.

Ich würde die Repositories i.d.R. An die benötigten Stellen injizieren lassen (siehe Beispiele oben). Der entsprechende Gültigkeitsbereich sollte im Container konfiguriert sein (z.B. pro HttpContext eine Instanz eines Typs). Der Container wird dafür sorgen, dass die Instanzen zur richtigen Zeit erzeugt und zerstört werden. Über das "immer und immer wieder neu" erzeugen würde ich mir zunächst keine Gedanken machen - in "normalen" Anwendungen ist das überhaupt kein Problem.

  1. Ich muß den frisch erzeugten Auftrag ja auch noch in der Datenbank speichern, ebenfalls per Repository.

Siehe die Beispiele oben. Der Auftrag sollte sich nicht selbst in das Repository speichern (außer natürlich, du verwendest das Active Record Pattern, was ich deinem Text nicht entnehmen konnte).

Um es zusammen zu fassen: nicht überall in der Anwendung, wo Objekte erzeugt werden, sollte generell der DI-Container zum Einsatz kommen. DI-Container sind super, um Infrastruktur und Services an die passende Stelle zu bekommen. Domain Entities und ähnliche transiente Objekte würde ich entweder einfach über new erstellen oder, wenn höhere Abstraktion angestrebt ist oder die Erzeugung komplex ist, über eine Factory.

Grüße,
Andre

5.941 Beiträge seit 2005
vor 13 Jahren

Hallo Andre

Auch wenn die meisten DI-Container beim Aufruf von Resolve die manuelle Übergabe von Parametern zulassen, rate ich persönlich davon ab, da meist mit Strings gearbeitet wird, um den Parametern Werte zuzuweisen, was leicht zu Laufzeitfehlern führt und schlecht zu refaktorisieren ist.

Strings? Beim Resolve-Aufruf?

Strings werden wenn, dann in der Xml-Konfiguration benutzt.
Beim Resolve-Aufruf kannst du das Argument typisiert übergeben und hast dadurch keine Refaktoring-Probleme.

Natürlich können da Runtime-Fehler auftreten, aber das geht nicht anders.
Und auch ist es klar, das man damit ein bisschen Unabhängigkeit auf den konkreten Typen verliert. Allerdings muss das nicht tragisch sein, wenn man das Wissen hat, das alle erbenden Klassen, die selben Argumente erwarten.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

1.373 Beiträge seit 2004
vor 13 Jahren

Strings? Beim Resolve-Aufruf?

Zumindest bei einigen DI-Container (u.a. bei Ninject über ConstructorArgument und Castle Windsor über ein Dictionary) werden explizite Parameter als Schlüssel-Wert Paare (also Argumentname, Wert) übergeben.

Bei LightCore scheint die Benennung expliziter Argumente optional zu sein (steht zumindest so unter Advanced Features). Das führt aber zu anderen Problemen, z.B. wenn sich die Reihenfolge der Parameter ändert.

Natürlich können da Runtime-Fehler auftreten, aber das geht nicht anders.
Und auch ist es klar, das man damit ein bisschen Unabhängigkeit auf den konkreten Typen verliert. Allerdings muss das nicht tragisch sein, wenn man das Wissen hat, das alle erbenden Klassen, die selben Argumente erwarten.

Deshalb rate ich davon ab, Parameter auf diese Art zu übergeben. Wenn man weiß, dass jede Implementation einer Abstraktion diese extra Parameter benötigt, würde ich immer zu einer extra Methode raten, mit der diese Daten explizit typ- und refaktorisierungssicher übergeben werden.

5.941 Beiträge seit 2005
vor 13 Jahren

Hallo Andre

Ach so, da habe ich zu wenig weit gedacht 😃.
Mit extra Methode meinst du eine Factory-Methode, oder wie kann ich mir das vorstellen?

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

1.373 Beiträge seit 2004
vor 13 Jahren

Mit extra Methode meinst du eine Factory-Methode, oder wie kann ich mir das vorstellen?

Nein, im Grunde eine Initialisierungsmethode.

Ich lehne mich hier einmal an das running example von Ninject an. Angenommen, ich habe ein interface ISword mit verschiedenen Implementierungen (Katana, Gladius), und ich weiß, dass jedes Schwert einen Namen besitzt, der für jede Instanz anders sein kann/soll. Das ist über den DI-Container nicht ohne weiteres zu konfigurieren.


interface ISword{}

class Katana :ISword {
  public Katana(string name){...}
}

class Gladius : ISword {
  public Gladius(string name){...}
}

Wenn ich das Schwert nun über den DI-Container erzeugen will, muss ich irgendwie den Parameter mitgeben. Beispiel Ninject:


var container = new StandardKernel();
container.Bind<ISword>().To<Gladius>();
...

var sword = container.Get<ISword>(new ConstructorArgument("name", "Excalibur"));

Das ganze ist jetzt wackelig, wenn eine andere Implementierung z.B. den Parameter nicht name sonder title nennt.

Wenn man aber weiß, dass der Name Teil eines jeden Schwertes ist, so wie du es sagst, dann schlage ich stattdessen so etwas for:


interface ISword {
  void Initialize(string name);
}

class Gladius : ISword {
  public Gladius(){
    // irgendwas, aber kein Argument
  }
}

class Katana : ISword {
  public Katana() { 
    // irgendwas, aber kein Argument
  }
}

Die Erstellung eines Schwertes erfolgt nun in 2 Schritten:


// 1. Instanziierung
var sword = container.Get<ISword>();

// 2. Initialisierung
sword.Initialize("Excalibur");

Das kann jetzt zur Laufzeit eigentlich nicht mehr überraschend fehlschlagen. Um zu garantieren, dass Initialize auch aufgerufen wird, würde ich hier eine Factory empfehlen:



public class SwordFactory {
  Func<ISword> createSword;

  public SwordFactory(Func<ISword> createSword){
    this.createSword = createSword;
  }

  public ISword Create(string name){
    var sword = createSword();
    sword.Initialiez(name);
    return sword;
  }
}

(NB: ich injiziere hier ein Func<ISword> statt z.B. einen IKernel, um eine Abhängigkeit zwischen Factory und DI-Framework zu vermeiden, etwa wie hier für Autofac beschrieben).

Die Factory kann man dann leicht selbst wieder vom DI-Container erstellen lassen und etwa in einen Service verwenden:


class SomeService {
  private SwordFactory factory;

  public SomeService(SwordFactory factory){
    this.factory = factory;
  }

  public void Foo(){
    var sword = factory.Create("Excalibur");
    sword.Attack();
  }
}

Grüße,
Andre

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo haarrrgh,

nur mal ganz pauschal der Einwurf: Eigentlich geht es dir doch um Persistenz, oder? Das reine Erzeugen von Objekten kommt doch in deinen eigentlichen Fragen gar nicht vor. Du willst kurz gesagt Objekte laden und speichern können. Natürlich muss beim Laden technisch gesehen ein Objekt im Speicher erzeugt werden, aber das ist eben nur eine rein technische Sicht. Konzeptionell geht es bei Laden darum, wieder Zugriff auf ein (im Hintergrundspeicher) schon vorhandenes, persistentes Objekt zu kommen.

DI/IoC brauchst du dabei eigentlich nicht, zumindest nicht vordergründig. Begriffe, die in dem Zusammenhang viel wichtiger sind: DAL, Persistenz, O/R-Mapper und ähnliches.

herbivore

5.941 Beiträge seit 2005
vor 13 Jahren

Salute Andre

Ach so meinst du das 😃
Eine sehr gute Lösung und auch ein Beispiel für eine sinnvolle Zusammenarbeit / Nutzung von einem DI-Container und Factories.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

1.044 Beiträge seit 2008
vor 13 Jahren

Hallo VizOne,

ich habe den Thread nur kurz überflogen. Bitte korrigiere mich, wenn ich was falsches sage.

Du hast in deinem Beispiel _Article _abstrahiert. Aus _Article _wird dann ein IArticle. Welchen Vorteil hat das? Keinen! Ich sehe zumindest kenen Vorteil. _Article _bleibt in meinen Augen ein Model und daran ändert sich auch nichts. Model-Klassen verwendet man in der kompletten Anwendung, ohne diese mit DI/IoC zu injizieren. Es gibt auch keinen Grund, Model-Klassen zu injizieren. Genau so ist es auch mit der Kunden-Klasse.

zero_x

1.373 Beiträge seit 2004
vor 13 Jahren

Du hast in deinem Beispiel _Article _abstrahiert. Aus _Article _wird dann ein IArticle. Welchen Vorteil hat das? Keinen! Ich sehe zumindest kenen Vorteil. _Article _bleibt in meinen Augen ein Model und daran ändert sich auch nichts.

Es gibt viele Gründe, warum man die Modellklassen abstrahieren möchte. Zum Beispiel, wenn man bestimmte Patterns anwenden möchte.

Zwei Beispiele:
NHibernate bietet Lazy Loading an, indem das Framework zur Laufzeit Proxyklassen für die einzelnen Modell-Klassen erzeugt, die erst dann auf die DB zugreifen, wenn es wirklich nötig ist. Das geht aber nur, wenn man entweder alle Methoden virtuell macht oder indem man stattdessen mit interfaces arbeitet. Letzteres umschifft einige lästige Probleme, wenn man mit Vererbungshierarchien arbeitet.
Ein anderes Beispiel wäre das Decorator Pattern.

Du siehst, dass man manchmal auch dann eventuell zu interfaces abstrahieren möchte, auch wenn es von der eigentlichen Geschäftslogik (Artikel) nur eine Implementierung gibt.

Model-Klassen verwendet man in der kompletten Anwendung, ohne diese mit DI/IoC zu injizieren. Es gibt auch keinen Grund, Model-Klassen zu injizieren. Genau so ist es auch mit der Kunden-Klasse.

Wenn du meinen Beitrag noch einmal in Ruhe liest, wirst du folgende Stelle finden:

Es ist allerdings in diesem konkreten Beispiel natürlich fraglich, ob ich überhaupt soweit gehen möchte und Artikel über einen DI-Container resolven möchte, oder anders: sollte ein Artikel (eine Domain Entity) ggf. benötigte Services überhaupt injiziert bekommen - DDD Puristen sagen nein (
>
), zumindest dann, wenn die Abhängigkeiten aus einer anderen logischen Schicht stammen (also z.B. aus der Infrastruktur).

In der Praxis ist es bei mir tatsächlich so, dass ich Domain-Objekte (Artikel, Kunde, Auftrag) in den meisten Fällen gar nicht über einen DI-Container erzeugen lasse, manchmal aber über eine Fabrik, da sie die physische Erzeugung (new XYZ) und die ggf. komplexe Initialisierung kapselt.

Grüße,
Andre

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Hallo zusammen,

vielen Dank für die vielen Antworten.

herbivore, ich fange mal mit Deiner Antwort an weil sie am schnellsten zu beantworten ist:

nur mal ganz pauschal der Einwurf: Eigentlich geht es dir doch um Persistenz, oder?
[...]
DI/IoC brauchst du dabei eigentlich nicht, zumindest nicht vordergründig. Begriffe, die in dem Zusammenhang viel wichtiger sind: DAL, Persistenz, O/R-Mapper und ähnliches.

Hm, vielleicht ist meine Intention nicht ganz rübergekommen.
Natürlich geht es (auch) um Persistenz, aber halt...wie soll ich es formulieren...im Gesamtbild betrachtet.

Am besten gebe ich mal ein kurzes Beispiel damit klar wird was ich meine.

Was wir jetzt haben, ist ungefähr sowas:
(wieder angelehnt an mein Beispiel mit Auftrag/Kunde/Artikel im Ausgangsbeitrag)

public class Auftrag
{
    public void NeuerAuftrag (int KundenNr, string ArtikelNr)
    {
        var kundeRepo = new KundeRepository();
        var artikelRepo = new ArtikelRepository();
    
        Kunde kunde = kundeRepo.Get(KundenNr);
        Artikel artikel = artikelRepo.Get(ArtikelNr);
    
        // und jetzt wird ein neuer Auftrag erzeugt, und
        // dabei auf die Properties von kunde und artikel zugegriffen...
    }
}

Sprich, es gibt schon einen DAL in Form dieser Repositories (die intern NHibernate benutzen).
Es geht mir also nicht darum wie man Objekte mit Daten aus der Datenbank füllt, wie Du anscheinend vermutet hast.

Sondern:
Eigentlich bin ich zum Thema DI/IoC gekommen weil ich angefangen habe mich mit Unit Tests zu beschäftigen.
Dabei habe ich schnell gemerkt daß man den obigen Code nicht wirklich gut testen kann, weil (um die Schlagwörter direkt mit einzubauen 😉 ) die Dependencies nicht injected werden, sondern das Objekt sie selber erzeugt.

Damit man diese Auftragsklasse vernünftig testen kann, darf sie also Kunde & Artikel nicht selber erzeugen, sondern diese derzeit fest verdrahteten Abhängigkeiten müssen von außen übergeben werden. Sprich, die Klasse braucht irgendeine Form von Dependency Injection.

Und damit sind wir im Grunde bei meinen Themen aus dem Ausgangsposting:

a) Mir ist klar wie ich Kunde & Artikel aus der Datenbank lade

b) Ich habe zahlreiche Beispiele zum Thema DI/IoC gelesen und (so hoffe ich) verstanden, bei denen aber immer nur "triviale" Objekte per DI injiziert worden sind, also welche die einfach so "aus dem Nichts" neu erzeugt werden konnten.

--> Und bei mir hat es halt noch nicht "Klick" gemacht, wie ich am besten a) und b) verbinde.
Also, wie ich aus einer Klasse wie dem oben gezeigten Auftrag mit Hilfe von DI/IoC die fest verdrahteten Abhängigkeiten entferne, wenn die zu injizierenden Objekte nicht einfach aus dem Nichts erzeugt werden können, sondern erst noch anhand von irgendwelchen Usereingaben aus der Datenbank geladen werden müssen.

Ich hoffe, jetzt ist es klarer rübergekommen.

Und jetzt versuche ich die Antworten von VizOne zu verstehen...

5.941 Beiträge seit 2005
vor 13 Jahren

Salute haarrrgh

Wieviele direkte Erzeugungen von Instanzen siehst du hier?


public class Auftrag
{
    public void NeuerAuftrag (int KundenNr, string ArtikelNr)
    {
        var kundeRepo = new KundeRepository();
        var artikelRepo = new ArtikelRepository();

        Kunde kunde = kundeRepo.Get(KundenNr);
        Artikel artikel = artikelRepo.Get(ArtikelNr);

        // und jetzt wird ein neuer Auftrag erzeugt, und
        // dabei auf die Properties von kunde und artikel zugegriffen...
    }
} 

Ich sehe genau zwei: kundeRepo und artikelRepo.
Diese Erzeugungen sollten zur Sicherstellung der Testbarkeit ausserhalb dieser Methode passieren und übergeben werden.

Ich würde - da ich noch weitere Konsumenten dieser Instanzen vermute - die Repositories als readonly-Membervariable der Klasse "Auftrag" hinterlegen und diese zu injizieren. Meine bevorzugte Variante ist die Constructor-Injection.

Dann kommen wir zu:

Kunde kunde = kundeRepo.Get(KundenNr);

Hier fungiert - wenn du so willst - das "kundeRepo" als Fabrik für deinen Kunden.
Die Fabrik wird injiziert, das heisst du hast Kontrolle darüber, wie sich die Fabrik verhält und was sie zurückgibt.

Dies ist also eine indirekte Erzeugung und muss nicht direkt von Aussen injiziert werden, auch nicht um die Testbarkeit sicher zu stellen.

Wenn du den Kunden / Artikel auf irgend eine Art verändern / faken willst oder musst - aus welchen Gründen auch immer, nutzt du in der Methode eine Abstraktion und kannst dann in deinem Test-Repository, das du reingibst, die gewünschte Implementation erzeugen.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Hallo VizOne,

ich habe alle Deine Antworten gelesen und bin auf folgendes gekommen.
Erstmal der Code, dann meine Gedanken dazu:

public interface IArtikelRepository
{
	IArtikel Get(string artikelNr);
}

public class ArtikelRepository
{
	public Artikel Get(string artikelNr)
	{
		// Artikel per NHibernate laden
	}
}

public interface IAuftragRepository
{
	int Save(IAuftrag auftrag);	
}

public class AuftragRepository
{
	public int Save(IAuftrag auftrag)
	{
		// Auftrag per NHibernate speichern
		// generierte Auftragsnummer zurückgeben
	}
}

public class AuftragService
{
	private IArtikelRepository artikelRepo;
	private IAuftragRepository auftragRepo;

	public AuftragService(IArtikelRepository artikelRepo, IAuftragRepository auftragRepo)
	{
		this.artikelRepo = artikelRepo;
		this.auftragRepo = auftragRepo;
	}
	
	public int CreateAuftrag(string artikelNr)
	{
		IArtikel artikel = this.artikelRepo.Get(ArtikelNr);
		
		var auftrag = new Auftrag();
		auftrag.ArtikelNr = artikel.ArtikelNr;
		// usw.
		
		return auftragRepo.Save(auftrag);
	}
}

a) Da der Auftrag sich nicht selber speichern darf (wir benutzen kein Active Record), habe ich jetzt doch eine "AuftragService"-Klasse gemacht.
Wir haben weder MVC noch eine nachrichtenorientierte Architektur, sondern eine verteilte Anwendung ähnlich Rainbirds Applikationsserver, da paßt ein "AuftragsService" ziemlich gut rein.

b) Ich habe die Factory weggelassen...denn wenn es ok ist, Domainobjekte auch direkt per "new" zu erzeugen, dann brauche ich die Factory eigentlich auch nicht.
Wie gesagt, es geht mir hier nicht um irgendwelche komplexen Erzeugungen neuer Objekte, sondern letzten Endes darum mir von meinem DAL Objekte mit Daten aus der Datenbank zurückgeben zu lassen.
Nach meinem Verständnis - aber ich lasse mich da gerne korrigieren - sind die Repositories also Abstraktion genug. Mit Factories würde ich dem Auftrag statt dem IArtikelRepository eine ArtikelFactory oder IArtikelFactory injizieren, und dann statt this.artikelRepo.Get einfach nur this.artikelFactory.Create aufrufen.
this.artikelFactory.Create würde aber nichts anders machen als this.artikelRepo.Get nochmal zu kapseln, und das erscheint mir hier unnötig.
Oder übersehe ich irgendwas?

(Daß man Domain-Objekte auch ohne DI erzeugen "darf" scheint für euch Experten auf dem Gebiet also klar zu sein - mir als DI-Anfänger war es nicht klar, aber diese Information macht das Verstehen des "Großen Ganzen" um einiges einfacher.
Es muß einem halt nur mal einer sagen 😁 )

c) Ist es ok, daß die Methode CreateAuftrag sich selbst den Artikel erzeugt?
Oder anders formuliert: DI bedeutet nicht unbedingt daß der AuftragService den kompletten fertigen Artikel von außen bekommt...der Artikel kann auch ruhig innerhalb der Klasse erst erzeugt werden solange dazu ein Repository oder eine Factory benutzt werden, die wiederum von außen injiziert werden.
Richtig?

d) Du hast geschrieben daß Du Domainobjekte zwar mit "new" oder per Factory erzeugst, aber gleichzeitig trotzdem noch per Interface abstrahierst.
Demzufolge benutze ich im AuftragService und im IArtikelRepository einen IArtikel, aber das konkrete ArtikelRepository gibt nicht das Interface, sondern direkt den Artikel zurück. So programmiere ich schon überall gegen Interfaces, aber im Repository benutze ich die konkrete Klasse weil intern eh immer ein Artikel geladen wird...von einem Artikel wird es auch immer nur die eine Ausprägung geben (und nicht wie z.B. in dem Ninject-Beispiel ISword --> Gladius UND Katana).

e) Analog Deinem eigenen Vorgehen:

...ein Objekt, dass mit dem DI-Container erzeugt wird, sollte bei der Erzeugung nur die Informationen benötigen, die der Container sich selbst zusammensuchen kann

...hat der Container in meinem Beispiel also nur die eine Aufgabe, dem AuftragService die konkreten Repositories zu injizieren, und sonst gar nichts.
Nach manchen Artikeln die ich gelesen habe hatte ich den Eindruck als sei der Container DAS zentrale Element der Applikation, über das sämtliche benutzten Objekte erzeugt werden, aber das war dann wohl auch ein bißchen übertrieben.

Macht das soweit alles Sinn, was ich mir da überlegt habe?

Nur eine Sache finde ich auch jetzt immer noch komisch, im Prinzip habe ich in meinem Ausgangsbeitrag in Frage 1 schonmal danach gefragt:
Der Konstruktor meines AuftragService hat in diesem Beispiel schon 2 Parameter, in Wirklichkeit wären es eigentlich 3 (ich brauche ja auch noch einen Kunden, also würde er auch noch ein IKundeRepository bekommen).
Wenn der AuftragsService noch mehr Methoden hat brauchen die vielleicht noch andere Repositories, das würde noch ein paar mehr Parameter bedeuten.
Gleichzeitig werden ArtikelRepository und KundeRepository in den anderen Methoden vielleicht gar nicht gebraucht.
Habe ich dann trotzdem nur einen Konstruktor mit allen Parametern und die werden bei jedem neuen AuftragService ALLE injiziert (und daß die Hälfte nicht gebraucht wird, macht nichts, weil die Erzeugung nicht viel kostet)?
Oder ist das ein Zeichen daß die AuftragService-Klasse zuviel macht und besser in mehrere Klassen aufgeteilt werden sollte?

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Hallo Peter,

danke für Deine Antwort!
Ich stelle mit Freude fest daß Du mehr oder weniger genau das geschrieben hast, worauf ich in den letzten 2 Stunden auch selber gekommen bin.
Das ist ein gutes Zeichen...anscheinend bin ich auf dem richtigen Weg 8)

5.941 Beiträge seit 2005
vor 13 Jahren

Salute haarrrgh

Ich stelle mit Freude fest daß Du mehr oder weniger genau das geschrieben hast, worauf ich in den letzten 2 Stunden auch selber gekommen bin.
Das ist ein gutes Zeichen...anscheinend bin ich auf dem richtigen Weg 8)

Ja, das ist doch super! 😃

c) Ist es ok, daß die Methode CreateAuftrag sich selbst den Artikel erzeugt?
Oder anders formuliert: DI bedeutet nicht unbedingt daß der AuftragService den kompletten fertigen Artikel von außen bekommt...der Artikel kann auch ruhig innerhalb der Klasse erst erzeugt werden solange dazu ein Repository oder eine Factory benutzt werden, die wiederum von außen injiziert werden.
Richtig?

Ja, genau!

Das ist das was ich mit oberem Beitrag sagen wollte 😃

e) Analog Deinem eigenen Vorgehen:

...ein Objekt, dass mit dem DI-Container erzeugt wird, sollte bei der Erzeugung nur die Informationen benötigen, die der Container sich selbst zusammensuchen kann

...hat der Container in meinem Beispiel also nur die eine Aufgabe, dem AuftragService die konkreten Repositories zu injizieren, und sonst gar nichts.
Nach manchen Artikeln die ich gelesen habe hatte ich den Eindruck als sei der Container DAS zentrale Element der Applikation, über das sämtliche benutzten Objekte erzeugt werden, aber das war dann wohl auch ein bißchen übertrieben.

Ich glaube VizOne ging es darum, wie sich der Container an sich verhalten soll, nicht die Benutzung dessen.
Trotzdem ist deine Frage legitim.

Der Container ist nicht wirklich das zentrale Element.
Er ist zwar immer da, wird / sollte / müsste aber nur als Startpunkt für einen Objektbaum angesehen werden.

D.h. am Beispiel von ASP.NET MVC, das alle benötigten (direkten) Abhängigkeiten über den Container injiziert werden. Haben diese Abhängigkeiten auch wieder (direkte) Abhängigkeiten, geht es weiter, bis die Sache befriedigt ist.

Für dich ist nur wichtig, das du schlussendlich dein Repository hast, das du nutzen kannst, das bspw. einen Logger enthält, den du global für die gesamte Anwendung auch einfach mal tauschen kannst.

IoC anwenden sollte man auch nur, wo es Sinn ergibt. Ein Beispiel für wenig bis keinen Sinn wäre mein oben stehender Beitrag mit den indirekten Abhängigkeiten.

Der Konstruktor meines AuftragService hat in diesem Beispiel schon 2 Parameter, in Wirklichkeit wären es eigentlich 3 (ich brauche ja auch noch einen Kunden, also würde er auch noch ein IKundeRepository bekommen).
Wenn der AuftragsService noch mehr Methoden hat brauchen die vielleicht noch andere Repositories, das würde noch ein paar mehr Parameter bedeuten.
Gleichzeitig werden ArtikelRepository und KundeRepository in den anderen Methoden vielleicht gar nicht gebraucht.
Habe ich dann trotzdem nur einen Konstruktor mit allen Parametern und die werden bei jedem neuen AuftragService ALLE injiziert (und daß die Hälfte nicht gebraucht wird, macht nichts, weil die Erzeugung nicht viel kostet)?
Oder ist das ein Zeichen daß die AuftragService-Klasse zuviel macht und besser in mehrere Klassen aufgeteilt werden sollte?

  1. Du solltest dir nicht unbedingt vorgängig Gedanken um die Performance machen, wenn du noch keine Probleme damit hast (premature optimization is the root of all evil.
  2. Trotzdem ist die Frage nicht unbekannt und es gibt Lösungen dazu.

Es kann ein Zeichen sein, das die Klasse zuviel (Mehr als eine) Verantwortlichkeit inne hält. In dem Fall sehe ich das sogar als deutliches Zeichen.

Wenn du eine Instanz vom Container evt. gar nicht oder nur lokal benötigst, gibt es auch Möglichkeiten.
Du kannst bei den meisten Containern entweder den Container selber (Meistens IContainer), eine Factory (In der Form von Func<KundenRepository>), oder sogar einer Lazy-Variante (Lazy<KundenRepository) - d.h. wenn einmal benutzt, wird die Instanz wiederverwendet - injizieren.

Damit kannst du dann nur lokal benötigte Instanzen erstellen, oder die Performance steigern.

Wie das ungefähr aussehen könnte, kannst du anhand von LightCore hier sehen:

  • (Im Moment ist die Seite nicht erreichbar, DNS-Probleme 😦).

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

1.373 Beiträge seit 2004
vor 13 Jahren

a) Da der Auftrag sich nicht selber speichern darf

Naja, "nicht darf" ist immer so dogmatisch. Es geht nur darum, dass sich der Auftrag nicht automatisch in das Repository eintragen sollte, da nicht jeder Auftrag unbedingt "verewigt" werden muss. Und wenn du eine gesonderte "Save" Methode anbietest, bist du ja schon irgendwo wieder bei Active Record.

Wir haben weder MVC noch eine nachrichtenorientierte Architektur, sondern eine verteilte Anwendung ähnlich
>
, da paßt ein "AuftragsService" ziemlich gut rein.

Ja, so ein Service-Layer ist wohl sinnvoll. Man muss nur immer aufpassen, dass man nicht zu viel Funktionalität in den SL statt in die Domain-Objekte packt, sonst gibt es das sogenannte Anämische Domain Modell. Meine Faustregel: der Service-Layer enthält nur die Logik, die die Kompetenzen eines einzelnen Objekts übersteigt. Er sucht sich die benötigten Objekte und Services zusammen und delegiert dann nur noch zwischen ihnen. Ich vermeide es übrigens auch Repositories in Domain Objekte zu injizieren.

b) Ich habe die Factory weggelassen...denn wenn es ok ist, Domainobjekte auch direkt per "new" zu erzeugen, dann brauche ich die Factory eigentlich auch nicht.

Genau richtig!

this.artikelFactory.Create würde aber nichts anders machen als this.artikelRepo.Get nochmal zu kapseln, und das erscheint mir hier unnötig.
Oder übersehe ich irgendwas?

Naja, eine Factory würde normalerweise kein Repository enthalten. Factory und Repository kümmern ich um verschiedene Zeitpunkte im Leben eines Objektes: die Factory erzeugt ein Objekt, das Repository enthält alle "lebenden" Objekte bis zu ihren "Tod" (zumindest gaukelt es das dem Benutzer vor - in Wirklichkeit sind viele der Objekte in einer DB auf Eis gelegt).

d) Du hast geschrieben daß Du Domainobjekte zwar mit "new" oder per Factory erzeugst, aber gleichzeitig trotzdem noch per Interface abstrahierst.

Ich finde es immer problematisch, solche Dinge zu pauschalisieren. Man sollte nicht gedankenlos wie ein wahnsinniger abstrahieren, nur weil es geht. Andererseits heißt eine der wichtigsten OO-Grundsätze: "Program to an interface, not to an implementation" Siehe hier.
Gleichzeitig erhöht die Einführung von Interfaces die Komplexität des Systems. Man muss also abwägen, wo der größte Vorteil liegt.

Demzufolge benutze ich im AuftragService und im IArtikelRepository einen IArtikel, aber das konkrete ArtikelRepository gibt nicht das Interface, sondern direkt den Artikel zurück. So programmiere ich schon überall gegen Interfaces, aber im Repository benutze ich die konkrete Klasse weil intern eh immer ein Artikel geladen wird...von einem Artikel wird es auch immer nur die eine Ausprägung geben (und nicht wie z.B. in dem Ninject-Beispiel ISword --> Gladius UND Katana).

Im Repository solltest du logischerweise gegen die Typen programmieren, den du von NHibernate zurückbekommst. Normalerweise also die konkreten Typen, Interfaces wenn du sie als Proxies verwendest.

e) Analog Deinem eigenen Vorgehen:
...hat der Container in meinem Beispiel also nur die eine Aufgabe, dem AuftragService die konkreten Repositories zu injizieren, und sonst gar nichts.

Exakt.

Nach manchen Artikeln die ich gelesen habe hatte ich den Eindruck als sei der Container DAS zentrale Element der Applikation, über das sämtliche benutzten Objekte erzeugt werden, aber das war dann wohl auch ein bißchen übertrieben.

Wahrscheinlich erklärt das die "Phobie" mancher DI-Gegner

Wenn der AuftragsService noch mehr Methoden hat brauchen die vielleicht noch andere Repositories, das würde noch ein paar mehr Parameter bedeuten.
Gleichzeitig werden ArtikelRepository und KundeRepository in den anderen Methoden vielleicht gar nicht gebraucht.

Wie Peter bereits sagt, ist das ein Zeichen dafür, dass das SRP missachtet wurde.

Mittlerweile bin ich von diesem starren Servicelayer ("KundenService") weggegangen und arbeite mit Commands und Commandhandlern:


public class ErstelleAuftragCommand {
  public string KundenNr{get;set;}
  public Position[] Positionen {get;set;}
  
  public class Position {
    public string ArtikelNr{get;set;}
    public int Anzahl{get;set;}
  }
}


public class ErstelleAuftragCommndHandler {
  private IKundenRepository kundenRepo;
  private IArtikelRepository artikelRepo;
  private IAuftragRepository auftragRepo;

  public void Handle(ErstelleAuftragCommand cmd){
    var kunde = kundenRepo.Get(cmd.KundenNr);
    var positionen = cmd.Positionen.Select(pos=>{
      var artikel = artikelRepo.Get(position.ArtikelNr);
      return new AuftragsPosition(artikel, pos.Anzahl);
    });

    var auftrag = new Auftrag(kunde, positionen);
    auftragRepo.Save(auftrag);
  }
}

... irgendwo in der UI oder im Controller o.ä.

class EineUIKlasse {
  ICommandBus bus;  

  public void BenutzerMöchteAuftragErstellen(){
    bus.Send(new ErstelleAuftragCommand (...));
    ...
  }

Ich mag diesen Ansatz: keine "Riesenservices" mehr, die CommandHandlers sind kleine Klassen, sie erfüllen einen einzelnen Zweck und jedes Command ist eine Super Boundary (Transaktion, Unit Of Work).

Grüße,
Andre

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Andre,

bezgl. ICommandBus: Der Ansatz gefällt mir, aber wie weiß der dass zum ErstelleAuftragCommand der ErstelleAuftragCommndHandler gehört? Oder konkreter gefragt: was passiert in der Send-Methode (einer konkreten Implementierung der Schnittstelle)?
Wird das auch aufgelöst oder geht das dirket?

Vielen Dank im Voraus!

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

1.373 Beiträge seit 2004
vor 13 Jahren

was passiert in der Send-Methode (einer konkreten Implementierung der Schnittstelle)? Wird das auch aufgelöst oder geht das dirket?

Prinzipiell passiert in Send Folgendes:

  1. Finde alle Handler, die den übergebenen Command-Typen abhandeln können.
  2. Erstelle eine Instanz des Handlers
  3. Ruf die Handle-Methode der Instanz auf und übergib ihr das Command-Objekt.

Bei 2 kann wenn benötigt ein DI-Container verwendet werden. Man könnte theoretisch auch die Handler einmal zu Beginn der Anwendung erstellen, aber das ist unpraktisch, da man dann keine kurzlebigen Objekte injizieren kann, z.B. eine Unit of Work.

In der Praxis mache ich es so: es gibt einen einfachen Dispatcher, bei dem IMessageHandler<T> registriert werden können, wobei T der Typ einer Nachricht (oder im Speziellen eines Commands) ist. Zudem gibt es eine Dispatch(object message) Methode. Der Dispatcher schaut sich den dynamischen Typ von message an, führt den nötigen downcast aus und ruft alle IMessageHandler<T>s für diesen Typ auf.

Das sieht in etwa so aus:


using System;
using System.Collections.Generic;
using System.Threading;

public interface IDispatcher {
  void Register<TMessage>(IMessageHandler<TMessage> handler) where TMessage : class;
  void Dispatch(object message);
}

public class Dispatcher : IDispatcher {
  private delegate void Handler(object message);

  private readonly ReaderWriterLockSlim syncLock = new ReaderWriterLockSlim();
  private readonly Dictionary<Type, List<Handler>> handlers = new Dictionary<Type, List<Handler>>();

  public void Register<TMessage>(IMessageHandler<TMessage> handler) where TMessage : class {
    syncLock.EnterWriteLock();
    try {
      var list = GetOrCreateHandlerList<TMessage>();
      list.Add(message => handler.Handle((TMessage)message));
    }
    finally {
      syncLock.ExitWriteLock();
    }
  }

  public void Dispatch(object message) {
    syncLock.EnterReadLock();
    try {
      List<Handler> list;
      if (handlers.TryGetValue(message.GetType(), out list)) {
        foreach (var action in list) {
          action(message);
        }
      }
    }
    finally {
      syncLock.ExitReadLock();
    }
  }

  private List<Handler> GetOrCreateHandlerList<TMessage>() {
    List<Handler> list;
    if (!handlers.TryGetValue(typeof(TMessage), out list)) {
      list = new List<Handler>();
      handlers[typeof(TMessage)] = list;
    }
    return list;
  }
}

Der Commandbus sieht dann z.B. so aus:


using System.Transactions;

public interface ICommandBus {
  void Send(object command);
}

public class InMemoryCommandBus : ICommandBus {
  private readonly IDispatcher dispatcher;
  private readonly IUnitOfWorkProvider unitOfWorkProvider;

  public InMemoryCommandBus(IDispatcher dispatcher, IUnitOfWorkProvider unitOfWorkProvider) {
    this.dispatcher = dispatcher;
    this.unitOfWorkProvider = unitOfWorkProvider;
  }

  public void Send(object command) {
    using (var scope = new TransactionScope())
    using (var uow = unitOfWorkProvider.Begin()) {
      dispatcher.Dispatch(command);
      uow.Accept();
      scope.Complete();
    }
  }
}

(IUnitOfWorkProvider sei hier ein Objekt, das neue UnitOfWorks öffnen kan).
Send sorgt hier für einen Contxt aus Transaktion und Unit Of Work und leitet das Command ansonsten direkt an den dispatcher weiter. Das bedeutet übrigens, dass, falls es untypischerweise mehrere Commandhandler für das gleiche Command gäbe, alle Handler in der selben Transaktion ausgeführt werden. Man muss selbst entscheiden, ob das die korrekte Semantik ist.

Jetzt fehlt noch ein Bindeglied: die CommandHandler müssen im Dispatcher registriert werden. Dazu verwende ich gerne ein wenig Reflection-Voodoo: alle Klassen zu finden, die einen oder mehrere IMessageHandler<T> implementieren ist leicht. Es wäre jetzt möglich, einfach die Handler zu instantiieren und beim Dispatcher zu registrieren. Es ist aber sinnvoller, die Handler bei jedem Dispatch neu zu erstellen, da man wie gesagt dabei dann kurzlebige Objekte (etwa eine Unit Of Work) injizieren kann. Also habe ich Wrapper-Handler, die das für mich übernehmen, und zwar einmal einen für IDisposable Handler und einen für Nicht-IDisposable Handler:


using System;

public abstract class HandlerInvoker<TMessage> : IMessageHandler<TMessage>
  where TMessage : class {
  private readonly Func<IMessageHandler<TMessage>> factory;

  protected HandlerInvoker(Func<IMessageHandler<TMessage>> factory) {
    this.factory = factory;
  }

  protected IMessageHandler<TMessage> InstantiateHandler() {
    return factory();
  }

  public abstract void Handle(TMessage message);
}

public class DisposableHandlerInvoker<TMessage> : HandlerInvoker<TMessage>
  where TMessage : class {

  public DisposableHandlerInvoker(Func<IMessageHandler<TMessage>> factory)
    : base(factory) {
  }

  public override void Handle(TMessage message) {
    var handler = base.InstantiateHandler();
    try {
      handler.Handle(message);
    }
    finally {
      ((IDisposable)handler).Dispose();
    }
  }
}

public class NonDisposableHandlerInvoker<TMessage> : HandlerInvoker<TMessage>
  where TMessage : class {
  public NonDisposableHandlerInvoker(Func<IMessageHandler<TMessage>> factory)
    : base(factory) {
  }

  public override void Handle(TMessage message) {
    InstantiateHandler().Handle(message);
  }
}


Besagtes Reflection Voodoo macht dann folgendes:

  1. Finde alle Typen, die ein oder mehrere IMessageHandler<T> implementieren.
  2. Für jedes implementiere IMessageHandler<T>, erstelle einen HandlerInvoker, wobei die übergebene factory-Func eine neue Instanz des gefunden Typen erstellt.
  3. Registriere alle auf diese Art gefundenen HandlerInvoker beim Dispatcher.

Ich spare mir hier die zwar interessante, aber etwas längliche Implementierung dieses Prozesses, es sei denn, jemand interessiert es.

Man muss nun nur noch den CommandBus und den Dispatcher beim DI-Container registrieren und dann einmal die Handler beim Dispatcher registrieren.

Grüße,
Andre

P.S.: auch wenn ich hier von einem CommandBus spreche, geht es hier ganz allgemein um einen (synchronen) Nachrichtenbus.

3.511 Beiträge seit 2005
vor 13 Jahren

Ich spare mir hier die zwar interessante, aber etwas längliche Implementierung dieses Prozesses, es sei denn, jemand interessiert es.

Ich finde den Ansatz auch recht interessant. Mich würde die Implementation schon interessieren.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

1.373 Beiträge seit 2004
vor 13 Jahren

Hi,

Ich habe mal ein etwas komplexeres Beispiel zusammengebastelt, siehe Anhang. Der erwähnte Reflection-lastige Teil befindet sich in Messaging\Registration des CommandBusApp.Core Projekts, insbesondere in MessageHandlerDiscovery. Siehe dazu auch die readme.txt.

Ansonsten zeigt der Code noch einige Möglichkeiten, wie man Systeme erweiterbar halten kann (Open-Closed Principle) sowie die Anwendung eines DI-Containers (hier: Ninject). Es ist übrigens zu erwähnen, dass das Core-Projekt nichts von einem DI-Container weiß.

Achtung: das ist kein Produktionscode, sondern einfach aus der Hüfte geschossen.

Grüße,
Andre

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Andre,

vielen Dank für die ausführliche Erklärung und das Demo!

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

3.511 Beiträge seit 2005
vor 13 Jahren

Hi,

ebenfalls ein großen Dank von mir. Schau ich mir heute abend in ruhe an.

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

1.373 Beiträge seit 2004
vor 13 Jahren

Gerne, und sorry an haarrrgh für die "feindliche Übernahme" des Threads.

Die CommandBus Sache ist natürlich nicht auf meinem Mist gewachsen, aber ich habe sie auf meinen Abenteuern im Bereich CQRS aufgegabelt und finde es eine super Idee, auch unabhängig von CQRS, d.h. als eleganter Ersatz für den typischen Servicelayer.

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Kein Problem...ich lerne ja auch noch was dabei 😁

Ich bin heute nur mal kurz über Dein Beispiel geflogen, habe aber auf den ersten Blick nicht viel davon verstanden. Ich muß mir das in den nächsten Tagen auch mal in Ruhe angucken...

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

So, zuerst mal habe ich nochmal ein paar Fragen zu Peter Buchers und VizOnes Ausführungen, bevor VizOne und sein Command Bus mit der feindlichen Übernahme angefangen haben 🙂

Wenn du eine Instanz vom Container evt. gar nicht oder nur lokal benötigst, gibt es auch Möglichkeiten.
Du kannst bei den meisten Containern entweder den Container selber (Meistens IContainer), eine Factory (In der Form von Func<KundenRepository>), oder sogar einer Lazy-Variante (Lazy<KundenRepository) - d.h. wenn einmal benutzt, wird die Instanz wiederverwendet - injizieren.

Damit kannst du dann nur lokal benötigte Instanzen erstellen, oder die Performance steigern.

Wie das ungefähr aussehen könnte, kannst du anhand von LightCore hier sehen:

  • (Im Moment ist die Seite nicht erreichbar, DNS-Probleme 😦).

Hmm...wieder auf mein Beispiel mit den Aufträgen bezogen...hast Du sowas gemeint?


public class AuftragService
{
    private IAuftragRepository auftragRepo;
    private IContainer container;

    public AuftragService(IAuftragRepository auftragRepo, IContainer container)
    {
        this.auftragRepo = auftragRepo;
        this.container = container;
    }

    public int CreateAuftrag(string artikelNr)
    {
        IArtikelRepository artikelRepo = container.Resolve<IArtikelRepository>();      

        IArtikel artikel = artikelRepo.Get(ArtikelNr);
        
        var auftrag = new Auftrag();
        auftrag.ArtikelNr = artikel.ArtikelNr;
        // usw.

        return this.auftragRepo.Save(auftrag);
    }

    public void DeleteAuftrag(int auftragsNr)
    {
        this.auftragRepo.Delete(auftragsNr);
    }
}

Das AuftragRepository wird direkt im Konstruktor injiziert, weil ich das definitiv sowieso in jeder Methode brauche.
Das ArtikelRepository (und das KundenRepository, das ich in diesem Beispiel der Übersichtlichkeit zuliebe weggelassen habe) brauche ich nicht in jeder Methode, also injiziere ich im Konstruktor den Container, mit dem ich mir die anderen Repositories bei Bedarf erzeuge.

Nachdem ich das bis hierhin geschrieben hatte, habe ich auch mal nach dem LightCore-Link gesucht den Du posten wolltest - das war der hier (erster Abschnitt, ähnliches Beispiel wie mein obiges), richtig?

Wenn mein Beispiel das ist was Du meintest, dann habe ich aber noch eine Frage:
Auch wenn ich den Container hier wenigstens nicht direkt anspreche sondern injizieren lasse, ein Service Locator ist das Ganze trotzdem.
Und es gibt ja auch genug Leute die sagen, Service Locator ist ein Anti-Pattern und sollte niemals und wirklich NIEMALS benutzt werden.

--> Ist das auch wieder so eine übertriebene Pauschalisierung wie z.B. auch das hier?

Nach manchen Artikeln die ich gelesen habe hatte ich den Eindruck als sei der Container DAS zentrale Element der Applikation, über das sämtliche benutzten Objekte erzeugt werden, aber das war dann wohl auch ein bißchen übertrieben.

Oder ist das im Fall des Service Locators wirklich berechtigt?
Ich sehe ein daß der Service Locator dem "Don't call us, we call you"-Prinzip widerspricht (das ist immer die Begründung warum der SL ein Anti-Pattern sein soll), aber ist das so schlimm?

Andererseits hat VizOne hier ein Code-Beispiel mit Commands und CommandHandlern gebracht.

Ich hatte irgendwann weiter oben schonmal geschrieben daß es für mich irgendwie komisch wirkt, meinen AuftragService in AuftragLöschService, AuftragNeuService usw. aufzuteilen.
Andererseits ist das ja genau der Hintergedanke hinter VizOnes ErstelleAuftragCommand (auch wenn ich den ganzen Teil mit CommandBus & CommandHandlern erstmal mental ausklammere, das ist mir noch ein bißchen zu hoch).
Und da bräuchte ich dann auch keinen Service Locator mehr, sondern kann den einzelnen Commands alle ihre Abhängigkeiten direkt per Konstruktor-Injection übergeben, ohne daß die Anzahl der Parameter pro Konstruktor explodiert.

Also ist das wirklich die beste Lösung für mein Problem?

Ich tue mich im Moment noch ein bißchen schwer mit der Anzahl an Klassen die dadurch entstehen würden, aber das ist wahrscheinlich normal wenn man von 1000 Zeilen langen Modulen mit x Funktionen (ja, Access/VBA!) kommt... 😄

5.941 Beiträge seit 2005
vor 13 Jahren

Salute haarrrgh

Hmm...wieder auf mein Beispiel mit den Aufträgen bezogen...hast Du sowas gemeint?

Ja genau, die Url auf lightcore stimmt auch, sowie auch der Absatz.
Weiter unten findest du noch die Möglichkteiten mit Func<T> und Lazy<T>.

Auch wenn ich den Container hier wenigstens nicht direkt anspreche sondern injizieren lasse, ein Service Locator ist das Ganze trotzdem.
Und es gibt ja auch genug Leute die sagen, Service Locator ist ein Anti-Pattern und sollte niemals und wirklich NIEMALS benutzt werden.

Ja, sozusagen ist das ein Service Locator, jedoch schöner, da nicht statisch und von Aussen schlussendlich auch änderbar.
Service Locator ist höchstens eine Art Antipattern, wenn du nicht DI nutzt, sondern überall nur Service Locator.

Aber besser überall Service Locator als gar nichts. Jedoch am besten überall DI und nur an den benötigten Stellen (Keine Kontrolle über die Instanziierung) Service Locator nutzen (bspw. in Attributen), dort geht es nicht anders.

In diesem Fall ist das also eine Kombination aus der impliziten und expliziten Anwendung. Und explizit (Service Locator) nur dort, wo es implizit (per DI) nicht mögich ist.
In diesem Fall ist das sicherlich kein Antipattern, sondern normale Vorgehensweise, die einzig mögliche. Ausser einer normalen Instanziierung 😉.

Ich sehe ein daß der Service Locator dem
>
-Prinzip widerspricht (das ist immer die Begründung warum der SL ein Anti-Pattern sein soll), aber ist das so schlimm?

Nein, siehe die Antwort oben. Die Hollywood Geschichte ist nur eine Analogie, um DI zu verstehen / verinnerlichen.

Also ist das wirklich die beste Lösung für mein Problem?

Das kann ich dir nicht sagen 😃.
Auch dort brauchst du ggf. auch noch Service Location, wenn du bspw. mit ASP.NET MVC arbeitest kommst du um Attribute nicht hin, und das du dort dann Service Location nutzen musst, daran hilft auch ein anderes Pattern nicht.

Der Vorschlag von VizOne hört sich auf jeden Fall interessant an. Allerdings würde ich mich mal auf ein paar neue Dinge beschränken, auf diese konzentrieren und diese umsetzen.
Also schrittweise rangehen und bedenken: Es gibt keine beste oder perfekte Lösung im eigentlichen Sinne. Und wenn, dann lohnt sich die Suche danach meistens nicht. 80 / 20 Prinzip 😃

Ich denke, wenn du die grundlegenden Dinge, die wir hier besprochen haben, verstanden hast - was ich durch deine Antworten annehme - bist du schon gut dabei.

Grüsse aus Luzern, Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Aber besser überall Service Locator als gar nichts. Jedoch am besten überall DI und nur an den benötigten Stellen (Keine Kontrolle über die Instanziierung) Service Locator nutzen (bspw. in Attributen), dort geht es nicht anders.

In diesem Fall ist das also eine Kombination aus der impliziten und expliziten Anwendung. Und explizit (Service Locator) nur dort, wo es implizit (per DI) nicht mögich ist.
In diesem Fall ist das sicherlich kein Antipattern, sondern normale Vorgehensweise, die einzig mögliche. Ausser einer normalen Instanziierung 😉.

Hm...ich glaube, ich habe noch gar nicht verstanden wie die implizite Anwendung / "richtige" DI mit einem Container überhaupt funktioniert.

Explizite Anwendung alias Service Locator ist klar - das ist immer wenn man den Container selber aufruft, sprich wenn irgendwo in einer Klasse per Container.Resolve die von ihr benötigte Abhängigkeit selber erzeugt wird.

Implizite Anwendung ist so wie ich es verstanden habe im wesentlichen Konstruktor-Injection. Die Klasse bekommt ihre Abhängigkeit von außen übergeben und hat keine Ahnung wo diese herkommt oder wer sie erzeugt hat.

In meinem Beispielcode aus meinem letzten Beitrag wäre das also dieser Konstruktor:


public class AuftragService
{
    public AuftragService(IAuftragRepository auftragRepo, IContainer container)
    {
        // usw.
    }
}

Zum "Warmwerden" hatte ich die Abhängigkeiten der AuftragService-Klasse erstmal manuell erzeugt und übergeben, also komplett ohne Container.
Das würde dann ungefähr so aussehen:


static void Main()
{
    string artikelNr = "4711";
    AuftragRepository repo = new AuftragRepository();

    // manuelle Injection
    AuftragService service = new AuftragService(repo);

    int auftragsNr = service.CreateAuftrag(artikelNr);
}

Im nächsten Schritt wollte ich die Injection dann mit einem Container machen.

Aber es scheitert schon daran, daß ich nicht verstehe wie die implizite Anwendung mit einem Container überhaupt funktionieren soll.

Daß ich meine Typen erstmal registrieren muß ist klar:


builder.Register<IAuftragRepository, AuftragRepository>();
usw.

Aber was dann?
Erzeuge ich den AuftragService auch schon über den Container? Wenn ja, wie mache ich das ohne container.Resolve zu benutzen?
Oder erzeuge ich den AuftragService mit new? Dann muß ich ihm aber sofort beim Erzeugen ein IAuftragRepository übergeben, und woher bekomme ich das ohne new oder container.Resolve zu benutzen?

Ich habe jetzt die Dokumentationen von mehreren Containern durchforstet, und in allen Codebeispielen die ich gefunden habe wird immer nur container.Resolve benutzt (z.B. hier bei LightCore).

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

container.Resolve

Das ist ja gerade ein Vorteil vom IoC-Container dass er dann die Abhängigkeiten für dich auflöst.

Einfaches Beispiel:


public interface IAddition { }
public class Addition : IAddition { }

public interface ISubtraction { }
public class Subtraction : ISubtraction {}

public interface ICalculator { }
public class Calculator : ICalculator
{
    private readonly IAddition _addition;
    private readonly ISubtraction _subtraction;

    public Calculator(IAddition addition, ISubtraction subtraction)
    {
        _addition     = addition;
        _subtraction = subtraction;
    }
}


iocContainer.Register<IAddition, Addition>();
iocContainer.Register<ISubtraction, Subtraction>();
iocContainer.Register<ICalculator, Calculator>();

ICalculator calculator = iocContainer.Resolve<ICalculator>();

So - was passiert dabei?
Zuerst werden alle Komponenten dem Container per Register bekannt gemacht. Um nun eine registrierte Instanz des ICalculator zu erhalten wird per Resolve dem Container mitgeteilt dass er die Abhängigkeiten auflösen (engl. to resolve) soll. Er schaut also was er auflösen soll -> ICalculator. Registriert dafür wurde Calculator und dessen Konstruktor benötigt IAddition und ISubtraction. Diese beiden sind dem Container auch bekannt also löst er diese auch auf und übergibt die registrierten Komponenten dem Konstruktor damit eine Instanz von Calculator erstellt werden kann.

Das wesentliche ist also dass alle benötigten Komponenten per Register bekannt gemacht werden um dann per Resolve die Instanzer erzeugen zu können.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Hm, anscheinend ist mein letzter Beitrag falsch rübergekommen.
Das was Du erklärt hast, also warum man die Sachen vorher registriert und was der Container eigentlich im Hintergrund macht wenn man per Resolve eine Instanz anfordert, das ist mir alles klar.

Mein Problem ist ein anderes.
Peter hatte in seinem letzten Beitrag ja folgendes geschrieben:

Service Locator ist höchstens eine Art Antipattern, wenn du nicht DI nutzt, sondern überall nur Service Locator.

Aber besser überall Service Locator als gar nichts. Jedoch am besten überall DI und nur an den benötigten Stellen (Keine Kontrolle über die Instanziierung) Service Locator nutzen (bspw. in Attributen), dort geht es nicht anders.

In diesem Fall ist das also eine Kombination aus der impliziten und expliziten Anwendung. Und explizit (Service Locator) nur dort, wo es implizit (per DI) nicht mögich ist.
In diesem Fall ist das sicherlich kein Antipattern, sondern normale Vorgehensweise, die einzig mögliche. Ausser einer normalen Instanziierung 😉

Mein Problem ist die Unterscheidung zwischen der impliziten und expliziten Anwendung.
Wie ein die explizite Anwendung (Service Locator / container.Resolve) funktioniert ist mir klar. Aber wie erzeuge ich ein Objekt implizit / per DI?

Container.Resolve kann es nicht sein da ich den Container ja nicht direkt aufrufen darf.
Mit new kann ich das Objekt auch nicht erzeugen, weil ich ja dann direkt eine Instanz des konkreten Typs erzeugen würde.

Aber wie sonst? Du (gfoidl) hast in Deinem Beispiel auch wieder einen Service Locator verwendet. Kannst Du mir ein Beispiel geben wie das Ganze ohne Service Locator aussehen würde?

Vielen Dank!

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo haarrrgh,

wie Peter Bucher schon schrieb: Es wird immer eine Mischung aus expliziter und impliziter Anwendung sein. Mindestens einen explizite Anwendung brauchst du. Die Abhängigkeiten (und nur die) werden dann implizit erzeugt. Genau das zeigt das Beispiel von gfoidl.

herbivore

F
10.010 Beiträge seit 2004
vor 13 Jahren

@haarrrgh:
Doch genau Resolve ist es.

Implizit bedeutet das der Erzeuger des Objektes per iocContainer.Resolve<> das Object mit seinen gesamten Abhängigkeiten erzeugt, und nicht das Object seine einzelnen Anhängigkeiten ( Services ) selber per iocContainer.Resolve<> holt.

Ein Objekt sollte nur dann iocContainer.Resolve<> kennen/benutzen, wenn es nicht anders geht ( z.b. Factory ).

Also nochmal iocContainer.Resolve<> wird immer bei der Erzeugung von Objekten gebraucht, aber im Normalfall muss nur eine ( explizite / implizite ) Factory überhaupt wissen das es einen Container gibt.

5.941 Beiträge seit 2005
vor 13 Jahren

Hallo haarrrgh

Genau, das was FZelle geschrieben hat geht in diese Richtung.
Ich versuche das mal einfach zu erklären.

Bei der impliziten Anwendung geht es darum, das du an einem Ort ein container.Resolve<IFoo>() drin hast. Das kannst du dir als Resolution-Root vorstellen, also die Wurzel, wo die Auflösung beginnt.

Wenn das jetzt nicht ein IFoo, sondern bspw. ein IController ist, enthält er und seine Objekte komplett alles, was nötig ist (Bei einem ASP.NET MVC Request).

Der Container löst nach diesem einen expliziten Aufruf dann implizit 1, oder soviele Abhängigkeiten und Unterabhängigkeiten auf, wie gefordert sind.

(Expliziter Resolve Aufruf: Resolve<MeinController>();)
IController
 - ICustomRepository (implizit)
   - ILogger (implizit)
 - ILogger (implizit)
 - ISessionService (implizit)

...

Bei expliziter Anwendung würde das CustomerRepository explizit einen Logger erstellen, usw.

Klarer?

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Aaaahh...JETZT hat es bei mir "Klick" gemacht.
Vielen Dank an euch alle! 👍

Mir war einfach nicht klar daß die pauschale Aussage "Service Locator sind böse" sich nicht auf den grundsätzlichen Gebrauch von Container.Resolve bezieht, sondern nur darauf, Container.Resolve direkt in allen Klassen zu benutzen anstatt die Abhängigkeiten über den Konstruktor zu übergeben.

Eine Frage habe ich in dem Zusammenhang aber trotzdem noch:

Bei der impliziten Anwendung geht es darum, das du an einem Ort ein container.Resolve<IFoo>() drin hast. Das kannst du dir als Resolution-Root vorstellen, also die Wurzel, wo die Auflösung beginnt.

Gibt es im Normalfall immer nur EINEN Resolution Root, oder viele?

Peter hat als Beispiel den Controller einer ASP.NET MVC-Anwendung genannt, aber mit ASP.NET MVC kenne ich mich nicht wirklich aus. Kommt man da wirklich mit einer einzigen Stelle in der ganzen Anwendung aus wo Container.Resolve benutzt wird?

Meine ersten Gehversuche mit DI/IoC mache ich gerade in Winforms-Anwendungen, und wenn ich da z.B. in mehreren verschiedenen Forms einen AuftragService brauche, wie komme ich an den?

In jedem Form Container.Resolve?

Für die Forms Interfaces schreiben, IAuftragService als Konstruktorparameter übergeben und das Winform an sich per Resolve erzeugen? (aber dann habe ich auch wieder an jeder Stelle wo ich ein Form lade Container.Resolve stehen)

Oder schreibe ich eine AuftragServiceFactory die intern Container.Resolve<IAuftragService> ausführt und benutze die überall?

Oder ist es gar nicht so schlimm wenn ich an X Stellen in der Anwendung direkt Container.Resolve aufrufe, solange es nur in der Infrastruktur und UI passiert und nicht in den eigentlichen Domain-Objekten?

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

wenn du dir mein obiges Bsp. anschaust - speziell iocContainer.Resolve<ICalculator>(); - dann ist das ein Resolution-Root.
Gibt es jetzt noch zB


public interface ILogger { }
public class MyLogger : ILogger { }

public interface IFooService { }
public class MyFooService : IFooService
{
    private readonly ILogger _logger;

    public MyFooService(ILogger logger) { ... }
}

dann muss der IFooService auch aufgelöst werden und dieser ist dann ein anderer Resolution-Root, da dieser der Ausgang für die Erzeugung von MyFooService ist und implizit wird ILogger aufgelöst.

Resolution-Root hat also nichts mit dem zu tun wie oft dieser im Code steht, sondern damit was als Wurzel für die implizite Auflösung verwendet wird. Es kann davon also durchaus mehrer geben. Die Resolution-Roots müssen extern aufgelöst werden.

Geh die letzten Beiträge nochmal durch dann sollten sich deine anderen Fragen klären.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

5.941 Beiträge seit 2005
vor 13 Jahren

Hallo haarrrgh

Vielfach reicht ein Resolution-Root, und ggf. ein paar Service Locator-Aufrufe.
Bei Windows Forms könntest du die Forms über container.Resolve<IMainForm>();, etc... auflösen.
Oder direkt über container.Resolve<MainForm>();, mit http://lightcore.ch/ ist es möglich, konkrete Klassen vom Container zu nutzen, das die Abhängigkeiten über den Konstruktor bekommen.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Ich fürchte, ich habe es immer noch nicht verstanden.

Wenn ich die Forms mit container.Resolve<Mainform>() öffne, dann brauche ich doch an JEDER Stelle, wo ich ein Form öffne, einen Service-Locator-Aufruf.

Peter schreibt, es können "ggf. ein paar Service Locator-Aufrufe" sein.
Bei dem Beispiel mit den WinForms klingt mir das in einer größeren Anwendung aber eher nach hunderten Service Locator-Aufrufen.

Und die sind auch noch kreuz und quer über die ganze Anwendung verteilt (vom Startform öffne ich ein Form mit einer Auftragsliste, von dort aus ein neues mit Details zu einem bestimmten Auftrag, von dort ein neues mit Details zu einer einzelnen Position...)

Jetzt könnte ich natürlich den Container irgendwie kapseln damit ich nur noch ein einer einzigen Stelle wirklich direkt den Container benutze.
Aber ob jetzt an 100 Stellen im Code container.Resolve<IForm>() steht, oder GlobaleWinFormFactory.Create<IForm>(), das macht auch keinen großen Unterschied.
Ich habe dadurch eigentlich nur eine fest verdrahtete Abhängigkeit durch eine andere ersetzt.

Das kann doch nicht der ganze Trick sein, oder?

1.378 Beiträge seit 2006
vor 13 Jahren

Aber du brauchst doch den Servicelocator nur dort, wo du erst zur Laufzeit weißt welche oder wieviele Objekte du brauchst.

Wenn ich schon im vorhinein weiß, dass mein "MainForm" ein "PopupForm" hat, dann lass ich das gleich im Konstruktor injecten und verwende nur diese eine Instanz weiter. Ich brauche also hier keinen ServiceLocator im MainForm.

Wenn ich aber eine beliebige Anzahl an Fenstern aus meiner "MainForm" aus öffnen kann, brauche ich eine Art Factory, die mir die Fenster erzeugt und diese wird wiederum den ServiceLocator behinhalten. Aber außer in Factories wirst du nirgends den ServiceLocator direkt brauchen.

Und gegen einer solchen Verwendung spricht mMn. nichts - man trennt die Zuständigkeiten und bleibt ausgezeichnet testbar.

Lg XXX

F
10.010 Beiträge seit 2004
vor 13 Jahren

Ich habe dadurch eigentlich nur eine fest verdrahtete Abhängigkeit durch eine andere ersetzt.

Wenn du nicht mit Interfaces arbeitest, dann ist das tatsächlich so.

Solltest du das mit den Interfaces und der Trennung dann aber irgendwann mal richtig verstanden haben, und verstanden haben das auch die Factory per DI Injected werden sollte, dann bestehen keine festen Abhängigkeiten, auch nicht zum SL.

H
haarrrgh Themenstarter:in
208 Beiträge seit 2008
vor 13 Jahren

Jetzt wo Du es geschrieben hast leuchtet es mir ein und ich verstehe es.
Es ist auch nicht so schwer zu verstehen...es muß einem einfach nur mal einer sagen.

Also:


    public class WinFormFactory : IWinFormFactory
    {
        private readonly IContainer container;

        public WinFormFactory(IContainer container)
        {
            this.container = container;
        }

        public T GetForm<T>()
        {
            return (T)container.Resolve<T>();
        }
    }

    public partial class MainForm : Form
    {
        private readonly IWinFormFactory factory;

        public MainForm(IWinFormFactory factory)
        {
            this.factory = factory;
            InitializeComponent();
        }
    }

    static class Program
    {
        static void Main()
        {
            var container = new Container();
            container.Register<MainForm>();

            Application.Run(container.Resolve<MainForm>());
        }

Dann kann ich hinterher irgendwo im MainForm folgendes machen:

        private void Button1_Click(object sender, EventArgs e)
        {
            var form = this.factory.GetForm<PopupForm>();
            form.Show();
        }

So habt ihr das gemeint, oder?

Zusätzliche Frage:
Ich habe in dem Code oben jetzt keine Interfaces für die WinForms benutzt, sondern benutze direkt die Forms selber.

Also nicht:

container.Register<IMainForm, MainForm>();
container.Resolve<IMainForm>();

sondern:

container.Register<MainForm>();
container.Resolve<MainForm>();

Macht es Sinn für die Forms auch Interfaces zu benutzen? Bei anderen Sachen (Repositories usw.) ist der Sinn klar, aber bei Forms habe ich es weggelassen weil ich den Sinn nicht sehe.
Ich möchte ja nicht zur Laufzeit das MainForm austauschen können o.ä., sondern ich benutze den Container nur damit das Form automatisch seine Abhängigkeiten bekommt.

F
10.010 Beiträge seit 2004
vor 13 Jahren

Ja, es macht sinn, sobald du auch mal das mit den Unittests machst.

Denn dann kannst Du die Logik deiner SW getrennt von der UI testen, denn du hast ja ganz sicher die UI von der Logik getrennt ( MVC/MVP/MVVM/MVVMP ).

Aber das mit den Interfaces steht eigentlich bei jeder Einleitung zu IOC.

S
443 Beiträge seit 2008
vor 13 Jahren

Grüss euch,

ich habe mir nun den ganzen Thread und alle verlinkten Seiten zur Gänze durchgelesen wie auch generell im Internet gestöbert.

Fazit:
IoC:


<component 
  id="internalname" 
  service="oRRoSoft.InterfaceWhichIsImplemented, oRRoSoft" 
  type="oRRoSoft.TypeWhichShouldBeResolved, oRRoSoft" />

ergibt:

IoCServiceLocator.Resolve<InterfaceWhichIsImplemented>();

DI besteht im eigentlichen Sinne nur aus einem ctor:

public DITestClass(IExternalDependency externalDependency) { /* code */ }

woher die Klasse die Instanze bekommt ist dem DI Prinzip egal.

Die Kombination draus wäre:

<component 
  id="dependecy" 
  service="oRRoSoft.IExternalDependency, oRRoSoft" 
  type="oRRoSoft.ExternalDependency, oRRoSoft" />
<component 
  id="internalname" 
  service="oRRoSoft.InterfaceWhichIsImplemented, oRRoSoft" 
  type="oRRoSoft.TypeWhichShouldBeResolved, oRRoSoft">
  <parameters>
    <externalDependency>${dependency}<externalDependency/>
  </parameters>
</component>

ergibt:

IoCServiceLocator.Resolve<InterfaceWhichIsImplemented>();

und die instanzierte Klasse bekommt vom IoC den fehlenden Parameter injected

Soweit habe ich es verstanden und ich glaube damit sind alle Fälle abgedeckt die man so im täglichen Leben braucht.

Und nun die Frage, für was brauche ich dann einen DI-Container?
Ich lese immer wieder von ihm, aber nirgends finde ich eine Erklärung wie das Ding aussieht oder was es eigentlich tut.
Habe ich mich irgendwo in der Begriffeflut verirrt oder verstehe ich nicht was es ist?

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen

F
10.010 Beiträge seit 2004
vor 13 Jahren

IOC == Inversion Of Control
Das ist der Name eines Pattern

DI-Container
Das ist eine Klasse die die Auflösung von Abhängigkeiten gemäß IOC erledigt.
Hier werden ggf ganze Trees erzeugt.

ServiceLocator
Eine weitere Klasse die es ermöglicht ein einzelnes Objekt gemäß IOC vorgaben zu erstellen.

S
443 Beiträge seit 2008
vor 13 Jahren

aahhh ...

Di-Container ist das Object das man braucht um IoC zu "erhalten".
Also sowas ähnliches wie mein Objekt mit dem Namen IoCServiceLocator.

Aber so wie Du es geschrieben hast, gibt es zwischen dem DI-Container und dem IoCServiceLocator einen Unterschied?
Kannst Du da eventuell noch einen Satz dazu loswerden?

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo spike24,

Aber so wie Du es geschrieben hast, gibt es zwischen dem DI-Container und dem IoCServiceLocator einen Unterschied?

alles schon ausführlich besprochen:

Service Locator und Dependency Injection Container: Was sind die Unterschiede? Wann was verwenden?

herbivore

S
443 Beiträge seit 2008
vor 13 Jahren

Ist mir da ein Beitrag ausgekommen, herzlichen Dank für den Link.
Denke jetzt habe ich es kapiert und kann es einsetzen.

🔝

mbg
Rossegger Robert
mehr fragen mehr wissen

Montag morgen ist die beste Zeit um eine erfolgreiche Woche zu beginnen