Abhängigkeitsgraphen aufbauen - Oberthema DI

14 views
Skip to first unread message

Ralf Westphal

unread,
Sep 13, 2008, 12:03:17 PM9/13/08
to altnetde
Hallo, Leute!

Heute möchte ich mal euren geschätzten Rat einholen. Als Alt.Netler
habt ihr Erfahrung mit DI bzw. Microkernel/Servicelocation. Das ist ja
auch ne nette Sache. Hier etwas "Pseudocode":

DIContainer.Instance.Map<IService, MyService>();
...
IService s = DIContainer.Instance.Get<IService>();

Das Mapping kann man entweder im Code anlegen oder in einer Mapping-
Datei (z.B. App.Config).
Bisher habe ich die Mapping-Datei vorgezogen - inzw. finde ich
allerdings ein imperatives Mapping in einer dedizierten Assembly
besser:

myapp.mapping.dll:

public static class Mapping
{
public static void Map()
{
IDIContainer c = DIContainer.Instance;
c.Map<IService, MyService>();
}
}

myapp.exe:

public class Program
{
public static void Main()
{
Mapping.Map();

IService s = DIContainer.Instance.Get<IService>();
...
}
}

Warum finde ich das imperative Mapping besser? Weil ich da
Intellisense habe. Der Nachteil, dass ich es zur Laufzeit bzw. auf der
Kundenmaschine nicht einfach mit notepad.exe ändern kann, wiegt für
mich (allermeistens) nicht schwer. Die Mapping-Assembly kennt also
alle Kontrakte und Implementationen von Komponenten.

Nun kommts: Bisher ist das Mapping aber sehr platt. Es wird im Grunde
nur ein Verzeichnis aufgebaut:

Interface -> Implementation

Das ist nicht mehr als ein Dictionary. Um innerhalb von Komponenten
Serviceimplementationen zu beschaffen, reicht es allerdings.

Aber warum dabei stehenbleiben? Warum nur ein Mapping aufbauen und
nicht einen ganzen Abhängigkeitsgraphen? Warum nicht den
Architekturentwurf - der in einem Abhängigkeitsdiagramm zwischen
Komponenten gipfelt - im Code materialisieren? Dann kann die
Architektur auch wieder aus dem Code re-generiert werden. Dann ist die
Wahrheit der Architektur auch im Code, selbst wenn der stark
entkoppelt ist über separate Kontrakte. Ein Analysewerkzeug wie
NDepend kann zwar schöne Abhängigkeitsdiagramme generieren - aber wo
keine Abhängigkeiten in Form von Assemblyreferenzen sind, da kommt es
nicht weiter. Solche Referenzen existieren nun allerdings nicht in
komponentenorientierter Software, wie andere und ich sie propagieren.

Wenn Komponente C die Komponente S braucht: C -> S, dann existieren
beide als eigene Assemblies: C.dll und S.dll. Doch nicht nur das.
Zumindest zu S gehört noch eine Kontrakt-Assembly, Sk.dll.
Um C echt von S zu entkoppeln, referenziert C.dll eben nicht S.dll,
sondern beide lediglich Sk.dll:

C.dll => Sk.dll
S.dll => Sk.dll

Erst zur Laufzeit macht dann ein DI Container S für C über das Mapping
von IService (definiert in Sk.dll) auf MyService (definiert in S.dll)
zugänglich.

Die architekturelle Abhängigkeit C -> S im Code im Grunde nicht mehr
Sichtbar. Das finde ich schlecht. Und wer nicht extra Architekturdoku
pflegen will, sollte das auch schlecht finden ;-)

Warum also nicht das Mapping aufbauen und gleichzeitig (!) einen
Abhängigkeitsgraphen der Komponenten?

Aber mit welchem API? Da suche ich euren Rat.

Nehmen wir mal folgende Komponenten an: A, B, C, D.
Mit diesen Abhängigkeiten:
A -> B
B -> C
B -> D

Kontrakte und Implementationen stehen in separaten Assemblies: A.dll,
Ak.dll, B.dll, Bk.dll usw.

Und je Komponente gibt es nur einen Dienst, der im Kontrakt durch ein
Interface beschrieben wird: ServiceA implementiert in A.dll und
definiert durch IServiceA in Ak.dll usw.

Ein normales Mapping würde nun so aussehen:

DIContainer.Instance.Map<IServiceA, ServiceA>();
DIContainer.Instance.Map<IServiceB, ServiceB>();
usw.

Und die Services würden sich über den DI Container bei Bedarf
Implementationen von weiteren Services beschaffen:

A.dll:

IServiceB b = DIContainer.Instance.Get<IServiceB>();

Die Abhängigkeitsinfo ist weg bzw. aufgelöst in Mapping am einen Ort
und Servicebeschaffungen an vielen anderen Orten.

Wie wäre es, wenn das Mapping aber zum Beispiel so aufgebaut wäre:

DIContainer.Instance.Root<IServiceA>().MapTo<ServiceA>()
.uses<IServiceB>().MapTo<ServiceB>();

oder kürzer:

DIContainer.Instance
.Root<IServiceA, ServiceA>()
.uses<IServiceB, ServiceB>();

Da steckt das Mapping drin und (!) die Abhängigkeit der Komponenten
voneinander.

Soweit ist das auch einfach zu machen. Aber ich würde gern weiter
"flüssig" schreiben. Wie sollte dann die zweifache Abhängigkeit der
Komponente B von C und D notiert werden?

So:

DIContainer.Instance
.Root<IServiceA, ServiceA>()
.uses<IServiceB, ServiceB>()
.uses<IServiceC, ServiceC>()
.andUses<IServiceD, ServiceD>();

Das ginge. Aber wie dann notieren, dass C von X abhängig ist? Das mit
dem "flüssigen" Hintereinanderschreiben hat so seine Grenzen, fürcht
ich. Wäre dann nicht gleich eine Schachtelung besser?

A
B
C
X
D

DIContainer.Instance
.Root<IServiceA, ServiceA>
(
DIContainer.Component<IServiceB, ServiceB>
(
DIContainer.Component<IServiceC, ServiceC>
(
DIContainer.Component<IServiceX, ServiceX>()
).and<IServiceD, ServiceD>()
)
);

Was meint ihr? Versteht ihr, worauf ich hinaus will? Wie sollte ein
möglichst "flüssiger"/intuitiver API dafür aussehen? Ich denke, das
Nachdenken lohnt. Denn als Gewinn winkt, dass sich die Architektur aus
dem Code generieren lässt:

Bitmap bmp = DIContainer.Instance.DependencyGraph.ToBitmap();

Oder gibt es das schon?

Cheerio!

Ralf

Stefan Lieser

unread,
Sep 13, 2008, 12:41:32 PM9/13/08
to altn...@googlegroups.com
Hallo Ralf,

> Oder gibt es das schon?

jeder vernünftige DI Container muss einen Graph über die Abhängigkeiten
aufbauen da mit Hilfe des Graphen durch pre-order Traversieren alle
erforderlichen Objekte instanziiert werden können. Castle Windsor legt
diesen Graph offen und du kannst ihn traversieren.


Herzliche Grüße,
Stefan Lieser

P.S. Erstellen des Bitmaps musst du noch ergänzen ;-)


using System;
using System.Text;
using Castle.Core;
using Castle.Windsor;

namespace TryIt
{
public class Dependencies
{
public interface IServiceA { }

public interface IServiceB { }

public interface IServiceC { }

public interface IServiceD { }

public class ServiceA : IServiceA
{
private IServiceB _serviceB;

public ServiceA(IServiceB serviceB) {
_serviceB = serviceB;
}
}

public class ServiceB : IServiceB
{
private IServiceC _serviceC;
private IServiceD _serviceD;

public ServiceB(IServiceC serviceC, IServiceD serviceD) {
_serviceC = serviceC;
_serviceD = serviceD;
}
}

public class ServiceC : IServiceC { }

public class ServiceD : IServiceD { }


public static void Example()
{
WindsorContainer container = new WindsorContainer();
container.AddComponent("A", typeof(IServiceA),
typeof(ServiceA));
container.AddComponent("B", typeof(IServiceB),
typeof(ServiceB));
container.AddComponent("C", typeof(IServiceC),
typeof(ServiceC));
container.AddComponent("D", typeof(IServiceD),
typeof(ServiceD));

var serviceA = container.Resolve<IServiceA>();

var nodes = container.Kernel.GraphNodes;
TraversTheGraph(nodes, 0);
}

public static void TraversTheGraph(GraphNode[] nodes, int level) {
foreach (var node in nodes) {
Console.WriteLine("{0}Node: '{1}':", Indent(level),
((ComponentModel)node).Name);
TraversTheGraph(node.Dependents, level + 1);
}
}

private static string Indent(int level) {
StringBuilder indent = new StringBuilder(level * 2);
for(int i = 0; i < level; i++) {
indent.Append(" ");
}
return indent.ToString();
}
}
}

Björn Rochel

unread,
Sep 13, 2008, 1:54:46 PM9/13/08
to altnetde
Hi Ralf,

soweit ich weiss gibt es das leider noch nicht, aber Teile dessen was
Dir vorschwebt findest Du bereits zum Beispiel in StructureMap oder
Castle Windsor.
Hier lässt sich auf jeden Fall der Abhängigkeitsgraph traversieren.

Ich denke aber die Idee ist im kommen, da ein Trend von Xml hin zu
leichter refaktorisierbaren Mappings im Code so ziemlich in allen
Projekten zu erkennen ist.

Hinsichtlich Deiner Frage, wie das ganze als API ausdrückbar sein
könnte, würde ich das Ganze mit nem ExpressionBuilder (http://
martinfowler.com/dslwip/ExpressionBuilder.html) und Nested Functions
(http://martinfowler.com/dslwip/NestedFunction.html) angehen. Das
ganze hat mir in der Vergangenheit bei einem ähnlichen Problem
geholfen (falls es Dich interessiert kannst Du hier ein wenig drüber
lesen: http://www.bjoernrochel.de/2008/08/10/implementing-a-fluent-api-for-the-ribbon/).
Das ganze geht mehr oder weniger in die Richtung, die Du schon
angedeutet hast . . .

>>DIContainer.Instance
>> .Root<IServiceA, ServiceA>
>> (
>> DIContainer.Component<IServiceB, ServiceB>
>> (
>> DIContainer.Component<IServiceC, ServiceC>
>> (
>> DIContainer.Component<IServiceX, ServiceX>()
>> ).and<IServiceD, ServiceD>()
>> )
>> );

Ich würde mir eine API in etwa so vorstellen (Ein wenig aus
verschiedenen DI-Containern zusammengeklaut :-) ):

container.Register(
Map.Service<IService>()
.ImplementedBy<Service>()
.DependsOn(
Map.Service<IService2>()
.ImplementedBy<Service2>()
));

Ist recht flexibel, aber wahrscheinlich nicht ganz so intuitiv wie ein
reines fluent api. Zudem ist die Evaluierungsreihenfolge hier auch
umgedreht, was für manche Entwickler zuerst zumindest verwirrend ist.
Ich versuche mal zu zeigen, wie man das ganze zusammenbauen könnte
(ohne auf einen bestimmten Container aufzusetzen). . .

class Container : IContainer
{
List<IMapping> _Mappings;

void Register(IMapping reg)
{
_Mappings.Add(reg);
}

void T Resolve<T>()
{
IMapping mapping = _Mappings.Find( reg => reg.ServiceType ==
typeof(T));
mapping.CreateInstance() as T;
}
}

class Map
{
MappingExpression<T> Service<T>()
{
return new MappingExpression<T>()
}
}

class MappingExpression<TService> : IMapping
{
Type _ServiceType;
Type _ImplementerType;
List<IMapping> _ChildMappings = new List<IMapping>();

MappingExpression()
{
_ServiceType = typeof(TService);
}

MappingExpression<TService> ImplementedBy<TImplementer>() where
TIImplementer : TService
{
_ImplementerType = typeof(TImplementer)
}

MappingExpression<TService> DependsOn(params IMapping[]
mappings)
{
foreach(IMapping mapping)
{
_ChildMappings.Add(mapping);
}
}

object CreateInstance()
{
object created = null;
List<object> parameters = new List<object>();

foreach(IMapping mapping in _ChildMappings)
{
parameters.Add(mapping.CreateInstance());
}

return Activator.CreateInstance(typeof(_ImplementerType,
parameters.ToArray());
}
}

Ich hab das ganze mit ner heissen Nadel zusammengestrickt, wird daher
wahrscheinlich ne ganze Menge Rechtschreibfehler enthalten :-). Es
fehlen natürlich auch ne ganze Menge verschiedenster Dinge (z.B. das
Thema Lifetime und Singletons), aber ich hoffe Dir ein wenig damit
geholfen zu haben.

Viele Grüße
Björn


Stefan Lieser

unread,
Sep 13, 2008, 2:01:37 PM9/13/08
to altn...@googlegroups.com
>>> DIContainer.Instance
>>> .Root<IServiceA, ServiceA>
>>> (
>>> DIContainer.Component<IServiceB, ServiceB>
>>> (
>>> DIContainer.Component<IServiceC, ServiceC>
>>> (
>>> DIContainer.Component<IServiceX, ServiceX>()
>>> ).and<IServiceD, ServiceD>()
>>> )
>>> );

Mir ist nicht klar warum die Abhängigkeiten in ihrer Baumstruktur
beschrieben werden sollen. Es genügt doch die verwendeten Interfaces
nebst ihrer konkreten Implementierung im Container zu definieren. Der
Graph kann daraus generiert werden. Oder habe ich da was falsch verstanden?


Herzliche Grüße,
Stefan Lieser

Björn Rochel

unread,
Sep 13, 2008, 4:07:51 PM9/13/08
to altnetde
Nein, im Grunde hast Du recht. Das ganze kann auch "seriell" gelöst
werden.

Ich finde es allerdings recht charmant, wenn Du Deinen
Abhängigkeitsbaum auch inline spezifizieren kannst und Ihn damit
direkt im Code sehen kannst (was auch bedeutet, dass Du Ihn nicht erst
traversieren musst, um zu wissen wie das ganze sich zur Laufzeit
darstellt).

Viele Grüße
Björn

Ralf Westphal

unread,
Sep 13, 2008, 5:01:23 PM9/13/08
to altnetde
Hi, Stefan!

Danke für deine Mühe. Der Code funktioniert bei mir auch.
Aber er zeigt leider nicht das Szenario, wie ich es mir vorstelle!

Windsor baut nämlich den Graphen nur aufgrund der über ctor-Injection
wahrgenommenen Abhängigkeiten auf.
Wenn ich also von DI umschalte auf Microkernel, d.h. statt

class ServiceA : IServiceA
{
private IServiceB _sb;
public ServiceA(IServiceB sb)
{
_sb = sb;
...

schreiben will:

class ServiceA : IServiceA
{
private IServiceB _sb;
public ServiceA()
{
_sb = DIContainer.Instance.Get<IServiceB>();
...

dann geht es nicht mehr. Windows traversiert also nicht Assembly-
Referenzen, sondern "nur" Injection-Referenzen.
Außerdem würde ich mich mit deinem Vorgehen auf Windsor festlegen.
Warum sollte ich das aber?
Ich hätte gern einen Dependency Graph & Mapping API unabhängig von
einem bestimmten DI Container - egal ob Ninject, Unity oder Windsor.

Cheerio!

Ralf


On 13 Sep., 18:41, Stefan Lieser <slie...@lieser-online.de> wrote:
> Hallo Ralf,
>
> > Oder gibt es das schon?
>
> jeder vernünftige DI Container muss einen Graph über die Abhängigkeiten
> aufbauen da mit Hilfe des Graphen durch pre-order Traversieren alle
> erforderlichen Objekte instanziiert werden können. Castle Windsor legt
> diesen Graph offen und du kannst ihn traversieren.

...

Ralf Westphal

unread,
Sep 13, 2008, 5:18:20 PM9/13/08
to altnetde
Hi, Björn!

Danke auch dir für deine Mühe!

On 13 Sep., 19:54, Björn Rochel <bjoern.roc...@googlemail.com> wrote:
> Hi Ralf,
...
> container.Register(
>      Map.Service<IService>()
>           .ImplementedBy<Service>()
>           .DependsOn(
>                 Map.Service<IService2>()
>                       .ImplementedBy<Service2>()
>           ));

Das DependsOn() finde ich gut. Es drückt ja das wahre Verhältnis
zwischen A und B bzw. B und C sowie D aus.
Ich mach mir mal Gedanken, wie ich es dann möglichst sprechend oder
auch kurz formulieren würde.
Aber das ist eine Feinheit. Der entscheidende Hinweis ist, die
Abhängigkeit auszuformulieren.


> Ist recht flexibel, aber wahrscheinlich nicht ganz so intuitiv wie ein
> reines fluent api. Zudem ist die Evaluierungsreihenfolge hier auch
> umgedreht, was für manche Entwickler zuerst zumindest verwirrend ist.

Hm... dass es von "hinten nach vorne und innen nach außen" geht, ist
doch nicht so wichtig, würd ich sagen.
Lesen (!) und verstehen kann man es von links nach rechts und außen
nach innen.


> Ich hab das ganze mit ner heissen Nadel zusammengestrickt,  wird daher
> wahrscheinlich ne ganze Menge Rechtschreibfehler enthalten :-). Es
> fehlen natürlich auch ne ganze Menge verschiedenster Dinge (z.B. das
> Thema Lifetime und Singletons), aber ich hoffe Dir ein wenig damit
> geholfen zu haben.

Singletons oder Namen sind nicht so wild. Die kann man auch anhängen:

Map.Service<IServiceA>()
.To<ServiceA>().AsSingleton().WithName("rootService")
.DependingOn(...);

Cheerio!

Ralf


>
> Viele Grüße
> Björn

Ralf Westphal

unread,
Sep 13, 2008, 5:20:04 PM9/13/08
to altnetde
Deine Annahme ist die Nutzung von DI. Darauf möchte ich mich aber
nicht verlassen.
Zum anderen hat die Beschreibung des Mappings keinen dokumentarischen
Charakter im Code - auch wenn der Graph generiert werden könnte.

On 13 Sep., 20:01, Stefan Lieser <slie...@lieser-online.de> wrote:

Stefan Lieser

unread,
Sep 14, 2008, 2:04:36 AM9/14/08
to altn...@googlegroups.com
Hallo Ralf,

> Windsor baut nämlich den Graphen nur aufgrund der über ctor-Injection
> wahrgenommenen Abhängigkeiten auf.
> Wenn ich also von DI umschalte auf Microkernel, d.h. statt

[...]


> class ServiceA : IServiceA
> {
> private IServiceB _sb;
> public ServiceA()
> {
> _sb = DIContainer.Instance.Get<IServiceB>();
> ...
>
> dann geht es nicht mehr. Windows traversiert also nicht Assembly-
> Referenzen, sondern "nur" Injection-Referenzen.

ah jetzt verstehe ich worauf du hinaus willst.

Ich sehe bei dem Ansatz folgende Probleme:

1) Mit "_sb = DIContainer.Instance.Get<IServiceB>();" handelst du dir
Probleme bei Unit Tests ein da statische Methoden nicht so einfach zu
mocken sind (meines Wissens nach geht das z.Zt. nur mit TypeMock).

2) Unabhängig von der Idee den Dependency Graph visualisieren zu wollen
wird die Abhängigkeit so auch beim Lesen des Codes nicht sofort
deutlich. Der Constructor spricht sozusagen nicht die ganze Wahrheit.

3) Du musst die Definition der Abhängigkeiten parallel zum Code pflegen
und aktuell halten. DRY - Don't Repeat Yourself.

4) Ich sehe das Problem dass dies in einem Projekt an dem mehrere
Entwickler arbeiten nicht durchzuhalten ist. Und sobald ein einziger
Fehler im Graph ist wird das Vertrauen in ihn rapide sinken. Zu lösen
ist das Problem meiner Ansicht nach nur durch automatisiertes Erstellen
des Graphen (z.B. durch Constructor Injection).


Natürlich bist du damit an ein konkretes DI Framework gebunden. Aber zum
einen wird man dies in einem Projekt in aller Regel nicht so oft (wenn
überhaupt!) austauschen. Zum anderen dürften wie schon geschrieben alle
Container den Graphen intern aufbauen ao dass der Ansatz prinzipiell mit
allen Container funktionieren sollte.


Herzliche Grüße,
Stefan

Björn Rochel

unread,
Sep 14, 2008, 5:36:51 AM9/14/08
to altnetde
Ich sehe das ähnlich wie Stefan ...

Meiner Meinung nach sollte man Dependency Lookup spärlich einsetzen,
da es in fast allen Fällen schwerer zu testen ist. Ich persönlich
setze das nur an Stellen ein, die Factory - Charakter haben und dann
aber auch noch mal mit einem kleinen Wrapper drum herum.
Beispielsweise so (falls das DI-Framework auf ein statisches Gateway
setzt.)

class SomeFactoryConsumer
{
IFactory _Factory;

void Do()
{
Product p = factory.Create(ProductType.Foo);
}
}

class Factory
{
Product Create(ProductType type)
{
return DI.Instance.Resolve<Product>(type.ToString());
}
}


Ich denke gute Praxis ist es den Abhängigkeitsgraph an einer Stelle zu
beschreiben (unabhängig davon, ob das per XML oder imperativ im Code
gemacht wird) und die so konfigurierten Objekte möglichst frei von
Abhängigkeiten zu einem bestimmten Container (oder Container-
spezifischen Attributen) zu halten. Meine persönliche Präferenz ist
daher fast immer der POCO Ansatz und Ctor-Injection . .

Viele Grüße
Björn

Ralf Westphal

unread,
Sep 14, 2008, 7:00:19 AM9/14/08
to altnetde
On 14 Sep., 08:04, Stefan Lieser <slie...@lieser-online.de> wrote:
...
> 1) Mit "_sb = DIContainer.Instance.Get<IServiceB>();" handelst du dir
> Probleme bei Unit Tests ein da statische Methoden nicht so einfach zu
> mocken sind (meines Wissens nach geht das z.Zt. nur mit TypeMock).

Probleme bei Unit Tests gibt es nicht. Ich mache das ständig so.
Außerdem kann ich immer noch beides machen:

class ServiceA : IServiceA
{
private IServiceB _b;

public ServiceA()
{
_b = CIContainer.Instance.Get<IServiceB>();
}

public ServiceA(IServiceB b)
{
_b = b;
}
...

Ich sehe das ganz entspannt - wie auch Martin Fowler (http://
martinfowler.com/articles/injection.html#UsingAServiceLocator).


>
> 2) Unabhängig von der Idee den Dependency Graph visualisieren zu wollen
> wird die Abhängigkeit so auch beim Lesen des Codes nicht sofort
> deutlich. Der Constructor spricht sozusagen nicht die ganze Wahrheit.

Sollte er?
Ich sage mal provokant: Dass eine Klasse c von einem Service s mit dem
Interface i abhängt, ist ein Implementationsdetail.
Warum sollte das auch nur irgendwie im expliziten Interface i oder im
impliziten Interface (zu dem der ctor gehört) zu sehen sein.
Das verletzt das Prinzip der Kapselung.

Wogegen ich mich wehre: Die sklavische Notwendigkeit, DI zu verwenden.
Und an "die Wahrheit des ctor" glaube ich nicht.


>
> 3) Du musst die Definition der Abhängigkeiten parallel zum Code pflegen
> und aktuell halten. DRY - Don't Repeat Yourself.

Achja... DRY... das ist schön. Sollte man auch immer im Hinterkopf
haben.
Aber genauso wie 100% Normalisierung nicht das Ziel sein können, so
ist auch DRY nur ein Leitstern, dem man folgen kann und soll - den man
am Ende aber dann doch womöglich nicht erreicht. De-Normalisierung ist
ok, wenn man weiß, warum. Un-DRY (oder moist) ist ok, wenn man weiß,
warum.

Und genauso, wie auch du noch überhaupt ein Mapping explizit
definierst (was nicht sein müsste, wenn du den Code ein wenig mehr
annotieren würdest), so würde ich gern ein wenig weiter gehen. Ein
Mapping wie heute üblich hat auch keinen weiteren Nährwert. Es ist nur
nervig.
Wenn allerdings das Mapping ein Abfallprodukt einer wertschöpfenden
Tätigkeit wie der Manifestation eines wesentlichen Architekturaspekts
wäre, das wäre ein Gewinn!


>
> 4) Ich sehe das Problem dass dies in einem Projekt an dem mehrere
> Entwickler arbeiten nicht durchzuhalten ist.

Warum?
Wo liegt die unüberwindbare Schwierigkeit gegenüber dem heute üblichen
Mapping?

Außerdem: der Abhängigkeitsgraph ist sehr stabil! Viel stabiler als
Kontrakte und Implementationen.

Und letztlich ist er auch nur eine weitere Komponente, die gepflegt
werden muss - die aber großen Gewinn bringt.


>Und sobald ein einziger
> Fehler im Graph ist wird das Vertrauen in ihn rapide sinken. Zu lösen
> ist das Problem meiner Ansicht nach nur durch automatisiertes Erstellen
> des Graphen (z.B. durch Constructor Injection).

Naja, sobald ein einziger Fehler im Mapping ist, hat dazu auch niemand
mehr Vertrauen.
Deshalb habe ich auch auf imperative Mappings bzw. Mappings im Code
umgestellt. Die helfen nämlich, Fehler zu vermeiden.
In einer XML-Datei, in der nichts geprüft wird, schleichen sich viel
schneller Fehler ein. Da reicht schon falsche Groß/Kleinschreibung.


>
> Natürlich bist du damit an ein konkretes DI Framework gebunden.

Nö.
Und selbst wenn. So what? Wie oft will ich denn das DI Fx wecheln?
Täglich, wie das Hemd?
Der Impact eines DI Fx im Code ist marginal: pro
Komponentenimplementation sind es vielleicht 2,3,4,5 Nutzungen, wenn
ich auch Service Location setze.
Falls diese Nutzungen dann mal ausgetauscht werden müssten... dann
hätte ich einen einmaligen Aufwand.
Eine vernachlässigbare Größe angesichts der Unwahrscheinlichkeit der
Ersetzung des DI Fx.
Außerdem: auch einen DI Fx kann ich kapseln. Also fiele dieser Aufwand
nicht mal an.


>Aber zum
> einen wird man dies in einem Projekt in aller Regel nicht so oft (wenn
> überhaupt!) austauschen.

Eben :-)

>Zum anderen dürften wie schon geschrieben alle
> Container den Graphen intern aufbauen ao dass der Ansatz prinzipiell mit
> allen Container funktionieren sollte.

Nutzt nur nix, weil ja ohne Nutzung von DI kein Graph zustande kommt.

Cheerio!

Ralf

>
> Herzliche Grüße,
> Stefan

Ralf Westphal

unread,
Sep 14, 2008, 7:28:21 AM9/14/08
to altnetde
On 14 Sep., 11:36, Björn Rochel <bjoern.roc...@googlemail.com> wrote:
...
> Ich denke gute Praxis ist es den Abhängigkeitsgraph an einer Stelle zu
> beschreiben (unabhängig davon, ob das per XML oder imperativ im Code
> gemacht wird)

Da stimme ich zu - auch wenn ich gerade zu einer imperativen Notation
tendiere.
Die gibt mir einfach mehr Hilfestellungen. DSL im Code ist immer noch
einfacher als XML-DSL.

>und die so konfigurierten Objekte möglichst frei von
> Abhängigkeiten zu einem bestimmten Container (oder Container-
> spezifischen Attributen) zu halten. Meine persönliche Präferenz ist
> daher fast immer der POCO Ansatz und Ctor-Injection .

Wenige Abhängigkeiten sind gut. Aber es gibt auch "Aspekte", von denen
kann man sich nicht unabhängig machen, zum Beispiel die grundlegende
Plattform. Dazu gehört bei .NET z.B. die GC oder die Erzeugung von
Objekten per new.

Unabhängigkeit steht immer auch im Spannungsverhältnis mit Effizienz.
Und Unabhängigkeit allein macht keinen Sinn. Sie braucht einen
Kontext. Warum sollte ich unabhängig sein?
Zum Beispiel für einfachere Testbarkeit oder Austauschbarkeit.

Wenn ich aber nicht austauschen möchte? Den DI Container sehe ich für
eine App auf der Ebene der Plattform. Wenn ich mich dafür entscheide,
dann ist er genauso konstant.

Und wenn ich kein Testproblem habe? Die ctor Injection kann z.B. ein
Problem machen, wenn es mehrere ctors gibt (z.B. weil ein
parameterloser ctor nötig ist). Dann muss man den DI ctor irgendwie
kenntlich machen. Schon ist sie dahin, die Unabhängigkeit.

Deshalb sehe ich es nicht so eng mit dem Wissen um einen DI container
im Code.

-Ralf

Stefan Lieser

unread,
Sep 14, 2008, 7:54:11 AM9/14/08
to altn...@googlegroups.com
Hallo Ralf,

Ralf Westphal schrieb:


> Probleme bei Unit Tests gibt es nicht. Ich mache das ständig so.
> Außerdem kann ich immer noch beides machen:

[...]


> Ich sehe das ganz entspannt - wie auch Martin Fowler (http://
> martinfowler.com/articles/injection.html#UsingAServiceLocator).

Einverstanden. Ist halt Geschmackssache.

>> 2) Unabhängig von der Idee den Dependency Graph visualisieren zu wollen
>> wird die Abhängigkeit so auch beim Lesen des Codes nicht sofort
>> deutlich. Der Constructor spricht sozusagen nicht die ganze Wahrheit.
>
> Sollte er?

Ich meine ja.

> Ich sage mal provokant: Dass eine Klasse c von einem Service s mit dem
> Interface i abhängt, ist ein Implementationsdetail.

Der Konsument von c sollte meiner Ansicht nach die Abhängigkeit kennen
denn er muss sie ja auflösen (sei es direkt, oder indirekt über ein DI
Framework). Wohlgemerkt der Konsument von c, nicht von ck (Kontrakt für
c) sollte es wissen!

> Warum sollte das auch nur irgendwie im expliziten Interface i oder im
> impliziten Interface (zu dem der ctor gehört) zu sehen sein.
> Das verletzt das Prinzip der Kapselung.

Völlig richtig! Ich sprach von einem Constructor und der taucht im
Interface nicht auf.

> Wogegen ich mich wehre: Die sklavische Notwendigkeit, DI zu verwenden.
> Und an "die Wahrheit des ctor" glaube ich nicht.

Es ist Geschmackssache, wie ich oben schon schrieb. Ich persönlich mag
die direkte Abhängigkeit im ctor eben viel lieber als die indirekte.

>> 3) Du musst die Definition der Abhängigkeiten parallel zum Code pflegen
>> und aktuell halten. DRY - Don't Repeat Yourself.
>
> Achja... DRY... das ist schön. Sollte man auch immer im Hinterkopf
> haben.

[...]


> Und genauso, wie auch du noch überhaupt ein Mapping explizit
> definierst (was nicht sein müsste, wenn du den Code ein wenig mehr
> annotieren würdest), so würde ich gern ein wenig weiter gehen. Ein
> Mapping wie heute üblich hat auch keinen weiteren Nährwert. Es ist nur
> nervig.

Wer sagt denn dass ich DI Mappings explizit definiere? Der Container
kann sie sich per Konventionen selber einsammeln.

> Wenn allerdings das Mapping ein Abfallprodukt einer wertschöpfenden
> Tätigkeit wie der Manifestation eines wesentlichen Architekturaspekts
> wäre, das wäre ein Gewinn!

Richtig.

>> 4) Ich sehe das Problem dass dies in einem Projekt an dem mehrere
>> Entwickler arbeiten nicht durchzuhalten ist.
>
> Warum?
> Wo liegt die unüberwindbare Schwierigkeit gegenüber dem heute üblichen
> Mapping?

Das Mapping wird per Unit Tests geprüft.

>> Und sobald ein einziger
>> Fehler im Graph ist wird das Vertrauen in ihn rapide sinken. Zu lösen
>> ist das Problem meiner Ansicht nach nur durch automatisiertes Erstellen
>> des Graphen (z.B. durch Constructor Injection).
>
> Naja, sobald ein einziger Fehler im Mapping ist, hat dazu auch niemand
> mehr Vertrauen.

Was der Unit Tests aber rechtzeitig entdeckt.

> Deshalb habe ich auch auf imperative Mappings bzw. Mappings im Code
> umgestellt. Die helfen nämlich, Fehler zu vermeiden.
> In einer XML-Datei, in der nichts geprüft wird, schleichen sich viel
> schneller Fehler ein. Da reicht schon falsche Groß/Kleinschreibung.

XML mag ich auch nicht.


Herzliche Grüße,
Stefan

Björn Rochel

unread,
Sep 14, 2008, 8:20:17 AM9/14/08
to altnetde
@Ralf

> Da stimme ich zu - auch wenn ich gerade zu einer imperativen Notation
> tendiere.
> Die gibt mir einfach mehr Hilfestellungen. DSL im Code ist immer noch
> einfacher als XML-DSL.

Absolut Deiner Meinung . . .

> Warum sollte ich unabhängig sein?
> Zum Beispiel für einfachere Testbarkeit oder Austauschbarkeit.

> Wenn ich aber nicht austauschen möchte? Den DI Container sehe ich für
> eine App auf der Ebene der Plattform. Wenn ich mich dafür entscheide,
> dann ist er genauso konstant.

... seh ich auch so . . . .

> Und wenn ich kein Testproblem habe? Die ctor Injection kann z.B. ein
> Problem machen, wenn es mehrere ctors gibt (z.B. weil ein
> parameterloser ctor nötig ist). Dann muss man den DI ctor irgendwie
> kenntlich machen. Schon ist sie dahin, die Unabhängigkeit.

.... das Problem hatte ich auch mal :-). Wird insbesondere dann
interessant wenn man bedenkt, dass sich unterschiedliche Container
auch gerne mal unterschiedlich in dem Zusammenhang verhalten (z.B.
StructureMap bevorzugt den Konstruktor mit den meisten Parametern,
während Castle Windsor den einfachsten bevorzugt). Ich glaube daher
auch nicht, dass es 100%ige Containerunhabhängigkeit gibt.

Ich glaube im Endeffekt ist doch vieles eine Frage persönlicher
Präferenzen bzw. Prioritäten. Wenn ich ein DI-Framework nutzen kann,
bevorzuge ich persönlich Dependency Injection vor Dependency Lookup.
Wahrscheinlich würde ich dann auch nicht mehrere Konstruktoren nutzen,
wenn ich es nicht müsste. Testbarkeit habe ich auch meistens mit einem
einzelnen Konstruktor, zumal die Sachen die mit DI erzeugt werden
wahrscheinlich auch nicht von anderem Consumercode erzeugt werden.

Sollte ich kein DI Framework zur Verfügung haben, oder aus
irgendwelchen Gründen nicht nutzen dürfen, ist die Situation natürlich
eine andere. Ich mach dann meistens folgendes:

class Service A : IServiceA
{
}

class ServiceB
{
ServiceB(IServiceA otherService)
{
_OtherService = otherService;
}

ServiceB() : this(new ServiceA())
{
}
}

Für mich ist Testbarkeit das wichtigste Kriterium. Wenn das ganze ohne
viel Aufwand testbar ist, ist das für mich völlig OK.
Softwareentwicklung ist ja eh nicht schwarz und nicht weiss. Von daher
empfinde ich oft Empfehlungen ala "Du must das so machen, weil X das
so macht / geschrieben hat" auch nicht so angemessen. Ein Teil der
Implementierungsaspekte wird immer abhängig von persönlichen Vorlieben
sein.
Von daher kann ich Deine Argumentation durchaus verstehen, obwohl ich
oft andere Präferenzen habe . . .

Viele Grüße
Björn


Björn Rochel

unread,
Sep 14, 2008, 8:22:57 AM9/14/08
to altnetde
> Wer sagt denn dass ich DI Mappings explizit definiere? Der Container
> kann sie sich per Konventionen selber einsammeln.

StructureMap und NInject können das übrigens :-)

Viele Grüße
Björn

Rainer Schuster

unread,
Sep 15, 2008, 3:59:35 AM9/15/08
to altnetde
So, nun will ich mich auch mal in die Diskussion einklinken.

Also das Problem den Graphen richtig verfolgen und erfassen zu können
kann ich gut nachvollziehen. Da habe ich mir schon zu C++ Zeiten einen
Kopf gemacht, wie die Funktionen aus den Dlls, die mit LoadLibrary
geladen, auch erfasst werden können um die Abhängigkeiten
darzustellen.

Die Konfiguration erstelle ich wenn möglich auch im Code. In einer
eigenen Assembly. So habe ich den Vorteil, Intellisense und alles
andere zu benutzen und über ein LazyLoading die Vorteile von XML zu
nutzen, ich kann sie mal schnell austauschen. Das ganze funktioniert
dann über eine Plugin-Architektur.

Das größte Problem bei XML ist, meiner Meinung nach, dass es sehr
"langatmig" ist. Ein Fluent-API ist in meinen Augen immer besser. Ein
gutes und aktuelles Beispiel dafür ist die Fluent-API von NH, das noch
als Contrib läuft. Gabriel hat ja hier schon in der NH-FAQ berichtet .
Das Tooling rund um XML ist zwar gut, aber das Refactoring ist
grausam. Da schleichen sich mal schnell Fehler ein. Die Compile-Time
überprüfung ist definitv der Grund schlechthin.

Zum Thema Service-Location bin ich etwas zwiegespalten. Das größte
Problem ist, das der Kontainer entweder statisch ist, oder überall
bekannt gemacht werden muss. Ein explizite Abhängigkeit. Ralph hat
hier schon argumentiert, dass auch dieser gekapselt werden kann. Das
sehe ich nur dann gegeben, wenn wir, wie aktuell ein externes DI-
Framework benutzen. Was aber wenn uns der Compiler irgendwann
unterstützt, oder implizit die Runtime, und wir das Framework nicht
mehr direkt ansprechen können. Ob wir so etwas benutzen wollen ist für
mich wieder eine andere Frage.

Ralf Westphal

unread,
Sep 15, 2008, 6:01:57 AM9/15/08
to altnetde
> Zum Thema Service-Location bin ich etwas zwiegespalten. Das größte
> Problem ist, das der Kontainer entweder statisch ist, oder überall
> bekannt gemacht werden muss. Ein explizite Abhängigkeit. Ralph hat
> hier schon argumentiert, dass auch dieser gekapselt werden kann. Das
> sehe ich nur dann gegeben, wenn wir, wie aktuell ein externes DI-
> Framework benutzen. Was aber wenn uns der Compiler irgendwann
> unterstützt, oder implizit die Runtime, und wir das Framework nicht
> mehr direkt ansprechen können. Ob wir so etwas benutzen wollen ist für
> mich wieder eine andere Frage.

Um die Diskussion etwas zu strukturieren:

Inzw. geht es um mehrerlei:

1. Meine Ausgangsfrage: Wie sollte ein API aussehen, der einen
Abhängigkeitsgraphen definiert?
Es sind sich alle einig, glaub ich, dass ein solcher Graph eine gute
Sache ist. Daraus kann man Archi-Dokumentation generieren, statt sie
von Hand zu pflegen.

Für mich ist die API-Frage schon ganz zu Anfang beantwortet worden mit
dem Hinweis auf explizite Modellierung von Mehrfachabhängigkeiten
durch Schachtelung.
Dafür herzlichen Dank.

2. Die daraus entstandene Frage: Muss man einen Abhängigkeitsgraphen
überhaupt explizit definieren?
Es ist zwar unzweifelhaft, dass ein DI Container zumindest intern
einen Abhängigkeitsgraphen aufbaut (sonst könnte er nicht autom.
injizieren) - aber die verschiedenen Container bieten unterschiedl
Möglichkeiten, darauf dann auch zuzugreifen.
Außerdem nutzt solch ein Zugriff nur etwas, wenn auch wirklich DI
benutzt wird, statt DL (Dependency Lookup).

Bottom line: Wenn man einen Abhängigkeitsgraphen haben will und sich
nicht auf einen DI Container festlegen möchte oder nicht nur DI,
sondern auch DL nutzt, dann kommt man um einen expliziten Graphen
nicht herum.

3. Die Frage, ob DI oder DL vorzuziehen sei?
Meine Meinung dazu habe ich hier kundgetan: ich nutze allermeistens
DL.
Aber aus Anlass dieser Diskussion habe ich in einem aktuellen kleinen
Projekt mal nur DI benutzt. Geht auch :-) Ist es aber einfacher? Fühle
ich mich damit besser? Hm... ne. Ist nur anders. (Das Mapping baue ich
natürlich imperativ auf.)

Bottom line: DI oder DL? Eher eine Geschmacksfrage, denn absolut
entscheidbar. Bei DL habe ich mehr Kontrolle über den Ort der
Beschaffung einer Abhängigkeit. Dafür zentralisiere ich bei DI die
Nutzung des Containers. Der wird nur für den Aufbau des Mappings
gebraucht. (Außer, ich muss ctors etc. mit containerspezifischen
Attributen markieren.)

Am Ende machts - wie immer - der Mix ;-)

4. Die Frage, ob Mapping im Code oder als XML?
Früher fand ich XML toll, wg last minute Änderungsmöglichkeit auch
nach der Compilation. Inzw. sehe ich den Vorteil nicht mehr so sehr,
sondern vor allem Nachteile. XML ist fehleranfälliger, weil ich darin
die Typnamen von Hand eintragen muss. Da schleicht sich schnell ein
Fehler ein. Das scheint mir hier auch durchaus Konsens zu sein.

Bottom line: Mapping im Code ist cool :-)

Meine Ausgangsfrage 1. ist beantwortet. Da werd ich demnächst mal
experimentieren in meinem aktuellen Projekt. Die Frage 3. hat mich
veranlasst, meine eigene Praxis ein wenig zu hinterfragen und mal was
anderes zu machen. Auch schön.

-Ralf

Rainer Schuster

unread,
Sep 15, 2008, 8:00:06 AM9/15/08
to altnetde
Mir sind da noch so ein paar Ideen gekommen.

Um einen solchen Graphen zu erstellen, wäre es ja dann eigentlich nur
unter folgenden Umständen möglich: statische oder dynamische analyse
(als Beispiel: FxCop vs. StyleCop)


1. Analyse des IL-Codes auf Assembly Ebene. Ich habe einen Testrun,
der 100% meines Codes abdeckt. Nach Ausführung des Codes, kann ich den
Graphen des Container abfragen. Jetzt sollten sich allle
abhängigkeiten innerhalb des DI-Framework-Graphen befinden. Das
Vorgehen ist dann sowohl auf DI als auch DL (auch im Mischbetrieb)
anwendbar.

2. Analyse des Sourcecode. Ein entsprechendes Tooling, das auf die
einzelnen DI-Frameworks (ihre Syntax) abgestimmt ist und den
Sourcecode durchsucht. (Kann NDepend entsprechend mit der CQL
instrumentiert werden? Hab damit keine Erfahrung). Das funktioniert
aber auch nur dann, wenn die Implementierung der
ImplementierungVonKontrakt.dll vorhanden ist. Soll heißen im Beispiel
von Ralph: nicht nur Ak.dll, sonder auch A.dll muss vorliegen.

Ralf Westphal

unread,
Sep 16, 2008, 7:55:40 AM9/16/08
to altnetde
On 15 Sep., 14:00, Rainer Schuster <Schuster-Rai...@web.de> wrote:
...
> 1. Analyse des IL-Codes auf Assembly Ebene. Ich habe einen Testrun,
> der 100% meines Codes abdeckt. Nach Ausführung des Codes, kann ich den
> Graphen des Container abfragen. Jetzt sollten sich allle
> abhängigkeiten innerhalb des DI-Framework-Graphen befinden. Das
> Vorgehen ist dann sowohl auf DI als auch DL (auch im Mischbetrieb)
> anwendbar.

So ganz allgemein find ich das ne interessante Idee. Das sind dann
wirklich die vollständigen und realen Abhängigkeiten :-)
Allerdings:
a. Ich muss dafür den Code laufen lassen. Für Dokumentationszwecke
finde ich das nicht so gut. Aber beim Debugging wäre es nützlich.
b. Das Ergebnis mag zu detailliert sein für den
Architekturblickwinkel. Der geht meiner Meinung nach nur bis zur
Komponentenebene. Wenn aber innerhalb von Komponenten DI eingesetzt
wird, dann zeigt der dynamische Abhängigkeitsgraph zuviele Details.

>
> 2. Analyse des Sourcecode. Ein entsprechendes Tooling, das auf die
> einzelnen DI-Frameworks (ihre Syntax) abgestimmt ist und den
> Sourcecode durchsucht. (Kann NDepend entsprechend mit der CQL
> instrumentiert werden? Hab damit keine Erfahrung). Das funktioniert
> aber auch nur dann, wenn die Implementierung der
> ImplementierungVonKontrakt.dll vorhanden ist. Soll heißen im Beispiel
> von Ralph: nicht nur Ak.dll, sonder auch A.dll muss vorliegen.

An sowas hab ich schonmal gearbeitet. Mit NDepend und CQL kann man
sich da auch etwas helfen. Im Code muss man dann aber z.B. mit
Attributen die Architektur beschreiben. Geht auch - aber ich finde den
gleichzeitigen Aufbau von Mapping und Abhängigkeitsgraph an einem Ort
irgendwie noch besser.

-Ralf

Rainer Schuster

unread,
Sep 16, 2008, 10:57:39 AM9/16/08
to altnetde
>b. Das Ergebnis mag zu detailliert sein für den
>Architekturblickwinkel. .Der geht meiner Meinung nach nur bis zur
>Komponentenebene. Wenn aber innerhalb von Komponenten DI eingesetzt
>wird, dann zeigt der dynamische Abhängigkeitsgraph zuviele Details.

Um über meine Architektur einen Überblick zu bekommen ist es aber doch
auch wichtig zu wissen, welche Komponenten denn andere Komponenten
benutzen. vielleicht ist das ja auch nur meine ganz persönliche
Meinung. Wenn aber z.B. meine Komponente OCR die Komponente
OpenXMLCreator benutzt um Text in Dateien zu speichern, finde ich das
schon spannend zu wissen.
Über DI kann ich das ja im Mapping mitteilen, wie du schon aufgezählt
hast (hier hast du dann wieder eine Übersicht des Ganzen an einer
Stelle). Wenn du aber DL machst, gibt es kein zentrales Mapping, das
dir sagt, es wird die Komponente OpenXMLCreator benötigt.
Wenn einen Komponente eine andere Komponenten benötigt, sollte mich
das schon interessieren, wenn es zu meiner Architektur gehört. Wie
weiß ich denn, wie die Komponente zu konfigurieren ist, wenn ich nicht
mal weiß, dass ich von ihr abhängig bin?

Ich gebe dir Recht, wenn du sagt, dass es dich auf Architekturebene
nicht interessiert (aber nur dann, wenn du die 50.000 Fuß ansicht von
oben hast). Ich bin der Meinung, der ObjektGraph, sollte wie in einem
TreeView auf- und zuklappbar zu sein, sodas die Granularität
eingestellt werden kann, wie in deinem Softwarezellenmodel.

Ich schlussfolgere also daraus, wir bräuchten für die Graphen einfach
verschiedene Detailstufen, die wir uns ausgeben lassen können.

> An sowas hab ich schonmal gearbeitet. Mit NDepend und CQL kann man
> sich da auch etwas helfen. Im Code muss man dann aber z.B. mit
> Attributen die Architektur beschreiben. Geht auch - aber ich finde den
> gleichzeitigen Aufbau von Mapping und Abhängigkeitsgraph an einem Ort
> irgendwie noch besser.


Jetzt will ich nochmal kurz auf dein Beispiel eingehen:

>class ServiceA : IServiceA
>{
> private IServiceB _b;
>
> public ServiceA()
> {
> _b = CIContainer.Instance.Get<IServiceB>();
> }
>
> public ServiceA(IServiceB b)
> {
> _b = b;
> }

Warum machst du eigentlich an dieser Stelle DL per ServiceLocator? Du
kannst ja dein Mapping genauso an einer Stelle machen. Und das auch
performant. z.B. mit Autofac. (aber Unity sollte das über die Config
genauso bieten). Hier wird zuerst ein Builder mit Lamba-Expressions
für deinen DI Container definiert

Schau dir mal diese Stelle in dem Beispiel zu Autofac auf codeproject
an: http://www.codeproject.com/KB/architecture/di-with-autofac.aspx#completing-the-example
Hier wird kein Autowiring gemacht, sondern ein manuelles Mapping an
zentraler stelle (was übrigens auch sehr viel performanter ist). Mal
schauen, was Nicholas Blumhardt von der Autofac Codebase in das MEF
mit einfließen lässt.

Du könntest trotzdem eine DL machen aber von zentraler stelle dein
Mapping erstellen.

Klasse A ändert sich wie folgt:

class ServiceA : IServiceA
{
private IServiceB _b;

public ServiceA(IServiceB b)
{
_b = b;
}

An der zentralen Stelle des Mappings sieht das dann so aus (Code von
Autofac)

builder.Register( CIContainer => new
ServiceA( CIContainer.Instance.Get<IServiceB>() ) );

Oder dann gleich komplett ohne ServiceLocation:

builder.Register( CIContainer => new
ServiceA( new ServiceBImplementation() ) );

Ob das dann noch einen Unterschied zu richtigem DI macht, weiß ich
nicht. Ist halt kein Autowiring, sondern handgemachtes DI per
konfiguration im Code. Das was du ja bevorzugst. Mir persönlich
gefällt DL nicht, weil du dir damit eine Abhängigkeit auf den DI
Container einbaust. Mit der von mir gezeigten Lösung entfällt deine
Abhängigkeit von DI Container. Die Abhängikeit zu ServiceB bleibt so
oder so.

Meiner Meinung nach spricht eigentlich nicht viel für DL, wie ich aus
meinem Blickwinkel gezeigt habe

Ralf Westphal

unread,
Sep 17, 2008, 12:47:00 PM9/17/08
to altnetde
Danke für deine Beispiele. Autofac schaue ich mir mal an.

Am Ende sind wir dann aber wohl bei der schon erwähnten
Geschmacksfrage angekommen.
Den Vorteil, den ich bei DL sehe, habe ich erklärt: Ich beschaffe
genau dann und dort, wo ich will. Ich muss ansonsten keinen Kompromiss
bei der Kapselung eingehen. Ich muss kein "Injektionsloch" aufmachen,
um etwas zu bekommen. Das ist einfacher zu verstehen, das ist einfach
zu pflegen, das ist einfach anzuwenden. Es ist auch einfach zu testen.
Da sehe ich nur marginalen Mehraufwand angesichts der Vorteile.

Aber es ist nicht der einzige Weg.

Wenn ich DL mache, gibt es übrigens genausoviel zentrales Mapping wie
bei DI. Ich benutze ja bisher dasselbe Mapping.
Nur möchte ich morgen eben etwas anderes tun: Mappen und (!) einen
Graphen dabei en passant definieren.

-Ralf

Jens Winter

unread,
Sep 23, 2008, 5:01:49 PM9/23/08
to altn...@googlegroups.com
Ralf Westphal wrote:
>> 3) Du musst die Definition der Abhängigkeiten parallel zum Code pflegen
>> und aktuell halten. DRY - Don't Repeat Yourself.
>
> Achja... DRY... das ist schön. Sollte man auch immer im Hinterkopf
> haben.
> Aber genauso wie 100% Normalisierung nicht das Ziel sein können, so
> ist auch DRY nur ein Leitstern, dem man folgen kann und soll - den man
> am Ende aber dann doch womöglich nicht erreicht. De-Normalisierung ist
> ok, wenn man weiß, warum. Un-DRY (oder moist) ist ok, wenn man weiß,
> warum.

Hallo Ralf,

Hier muss ich dir widersprechen. DRY ist ein Prinzip, welches stets zu
100% erfüllt sein sollte.

Es gibt Situationen, wo die Verletzung von DRY (bewusst oder unbewusst)
trotzdem funktionieren. Allerdings sind das entweder reine Glücksfälle
oder Fälle bei denen die Auswirkungen unkritisch sind.

Deine Idee der Generierung einer Architekturdoku, die auf einer
Verletzung von DRY basiert, würde ich als gewagt bezeichnen.

Ciao,
Jens

Jens Winter

unread,
Sep 23, 2008, 5:47:05 PM9/23/08
to altn...@googlegroups.com

Hallo Ralf,

Das ist eine sehr interessante Diskussion!

Meine Meinung: Die Idee, einen Abhängigkeitsgraphen erzeugen zu lassen
klingt sehr verlockend. Auf absehbare Zeit fehlen uns aber trotzdem die
Mittel dafür.

Abhängigkeiten zwischen Komponenten, und somit die Architektur, sind im
Quelltext manifestiert. Auch Mapping-Dateien von DI-Containern sind in
meinen Augen Quelltext.
Was ist mit Abhängigkeiten, die nicht per Container injiziert werden und
auch ansonsten über keine Factory erzeugt werden? Sie können nur durch
Code-Analyse oder per "NDepend-Analyse" dokumentiert werden. Ein Versuch
würde darin münden, nur ein einziges new() zuzulassen. Nämlich dem einer
Mutter-Factory oder eines Containers.

Ein Gedanke zum DI-Container:
Ein DI-Container kann für die Generierung des Graphen nur verwendet
werden, wenn alle Komponenten durch ihn erzeugt werden. Ich kann nicht
sehen, dass sich soetwas durchsetzen lässt.
Der Container ist ansonsten nur ein Tool, das beim Injizieren von
Abhängigkeiten unterstützt. Somit ist die Verwendung eines DI-Containers
rein taktischer Natur. Um die Idee des Aufbaus eines
Abhängigkeitsgraphen "sauber" zu halten, müsste man den Container also
erstmal ausblenden. So kann die Diskussion einen strategischeren
Charakter bekommen.

Das größte Problem, das ich sehe, ist die bereits angesprochene
Verletzung von DRY. Für das Vorhaben der Verwendung einer API wäre mir
das aber entschieden zu teuer.


Ciao,
Jens

Ralf Westphal

unread,
Sep 24, 2008, 8:03:19 AM9/24/08
to altnetde
Hallo, Jens!

On 23 Sep., 23:47, Jens Winter <jens.win...@gmail.com> wrote:
...

> Meine Meinung: Die Idee, einen Abhängigkeitsgraphen erzeugen zu lassen
> klingt sehr verlockend. Auf absehbare Zeit fehlen uns aber trotzdem die
> Mittel dafür.

Es geht nicht um "vollständige" Abhängigkeiten. Sie das Problem nicht
zu umfassend.
Mir liegen erstmal nur architekturrelevante statische Abhängigkeiten
am Herzen.
Das DI Mapping verschenkt eine Chance, die festzuhalten.


> Ein DI-Container kann für die Generierung des Graphen nur verwendet
> werden, wenn alle Komponenten durch ihn erzeugt werden.

Nach meine Definition sind Komponenten Komponenten, eben weil sie
durch DI/DL erzeugt werden. Alles andere verdient den Begriff
Komponente nicht so recht.
Was hebt eine Komponente über eine Assembly? Der separate Kontrakt
(eigene Assembly) und damit die Notwendigkeit (!), per DI/DL daraus
Klassen zu instanzieren.

Ich tendiere sogar dahin zu sagen, dass nur Komponenten über einen DI-
Container instanziert werden. Aber was heißt es eigentl, eine
"Komponenten zu instanzieren"? Jeder Komponente sollte/könnte
mindestens eine Komponentenklasse enthalten, deren Interface im
Kontrakt beschrieben ist.
Diese "Home-Klasse" ist die Wurzel der Komponente, ihr Repräsentant im
Speicher. Jede Instanzierung der Klasse steht für eine
Komponenteninstanz.
Andere Klassen aus der Komponente werden dann nicht via DI, sondern
über explizite Factory-Methoden der Komponente instanziert.

Diese Definition für Komponente empfinde ich als sauber. Sozusagen die
Mindestanforderung. (Ok, wenn dann auch noch andere Klassen aus der
Komponente per DI/DL instanziert werden, ist das auch nicht so
schlimm ;-) Aber die "Home-Klasse" finde ich wichtig. Sie kann Factory
und/oder Service sein. Aber sie sollte da sein.)


>Ich kann nicht
> sehen, dass sich soetwas durchsetzen lässt.
> Der Container ist ansonsten nur ein Tool, das beim Injizieren von
> Abhängigkeiten unterstützt. Somit ist die Verwendung eines DI-Containers
> rein taktischer Natur. Um die Idee des Aufbaus eines
> Abhängigkeitsgraphen "sauber" zu halten, müsste man den Container also
> erstmal ausblenden. So kann die Diskussion einen strategischeren
> Charakter bekommen.

Der DI-Container ist schon ausgeblendet, würd ich sagen. Der
Abhängigkeitsgraph kann in ein DI-Mapping überführt werden. Aber der
DI-Container muss nicht Teil des DI-Graphen sein. Ich sehe da ein
Providermodell: Abhängigkeitsgraphen aufbauen, dann einen Unity- oder
Ninject- oder Spring-Provider wählen und in ein passendes Mapping
transformieren.


>
> Das größte Problem, das ich sehe, ist die bereits angesprochene
> Verletzung von DRY. Für das Vorhaben der Verwendung einer API wäre mir
> das aber entschieden zu teuer.

Dazu hab ich ja schon etwas gesagt: Die Wiederholung ist einfach nicht
zu sehen. Denn nicht alle DI-Container bauen das Mapping zugreifbar
auf.

Mit der Transformation Abhängigkeitsgraph->Mapping entfällt das
Argument dann aber auch endgültig. Dann gibt es nur den
Abhängigkeitsgraphen. Die "Wiederholung" für einen anderen Zweck würde
daraus generiert. Das ist DRY.

-Ralf

Jens Winter

unread,
Sep 24, 2008, 9:43:18 AM9/24/08
to altn...@googlegroups.com
Hallo Ralf,

So langsam verstehe ich, worauf du hinaus willst. Ich benötige aber wohl noch ein paar Tage... :)

Nur noch einmal kurz zu DRY. Hier die sich wiederholenden Informationen:


DIContainer.Instance
    .Root<IServiceA, ServiceA>()
        .uses<IServiceB, ServiceB>(); // <== Stelle 1

class ServiceA : IServiceA
{
    private IServiceB _sb; // <== Stelle 2a

    public ServiceA()
    {
        _sb = DIContainer.Instance.Get<IServiceB>(); // <== Stelle 2b
    }
}

Die Abhängigkeit taucht an zwei Stellen auf: In der Container-Konfiguration und in der Implementation der Klasse.
Wenn ich nämlich stattdessen folgendes hätte, würde mich der generierte Abhängigkeitsgraph anlügen:


DIContainer.Instance
    .Root<IServiceA, ServiceA>()
        .uses<IServiceB, ServiceB>();

class ServiceA : IServiceA
{
}

Kennst du das Managed Extensibility Framework (MEF) schon?
http://www.codeplex.com/mef
Vor kurzem gab es den ersten Code Drop. Ich habe ihn mir schon kurz angeschaut. Ich kann mir vorstellen, dass MEF für die diskutierte Idee nützlich ist. Muss aber noch weitere Anforschungen anstellen.

Ciao,
Jens

2008/9/24 Ralf Westphal <in...@ralfw.de>

Jens Winter

unread,
Sep 25, 2008, 4:04:24 AM9/25/08
to altn...@googlegroups.com

Hallo!

Ich bin gerade auf dieses Projekt gestoßen:

Tools for Framework Architecture Verification
http://code.msdn.microsoft.com/fxarch

"This is a suite of tools primarily aimed at validating the architecture of a framework.

Currently, there are three tools:

Deps: produces dependency diagrams between assemblies, based both on implementation, as well as API-only.
Layering: verifies that a layering and grouping diagram is indeed satisfied by a set of assemblies
PotentialCallers: lists all the methods that call, directly or indirectly, a set of methods."

Das könnte für dich von Interesse sein.

Ciao,
Jens

2008/9/13 Ralf Westphal <in...@ralfw.de>

Sebastian Jancke

unread,
Sep 25, 2008, 4:23:02 AM9/25/08
to altn...@googlegroups.com
Es gibt sowas auch für Java, heißt glaube ich Structure101.

Ralf Westphal

unread,
Sep 25, 2008, 6:44:19 AM9/25/08
to altnetde
Danke für den Hinweis. Aber ich denke nicht, dass das ausreicht.
Microsoft denkt anders:

1. Microsoft denkt im Allgemeinen nicht an eine Trennung von Kontrakt
und Implementation.
2. Microsoft denkt im Allgemeinen nicht an eine Trennung von Projekten
einer Applikation in viele Projektmappen.

Die Tool, die Microsoft liefert, werden also nicht die Beziehung:

A.dll -> Bk.dll <- B.dll

in einer Grafik darstellen können. Es gibt keine statische Beziehung
zwischen A und B. Die ist nur indirekt über den Kontrakt Bk.
Deshalb reicht ja auch NDepend nicht, das ja schon lange
Architekturdarstellungen machen kann.

-Ralf

On 25 Sep., 10:04, "Jens Winter" <jens.win...@gmail.com> wrote:
> Hallo!
>
> Ich bin gerade auf dieses Projekt gestoßen:
>
> Tools for Framework Architecture Verificationhttp://code.msdn.microsoft.com/fxarch

Ralf Westphal

unread,
Sep 25, 2008, 6:59:40 AM9/25/08
to altnetde
> DIContainer.Instance
>     .Root<IServiceA, ServiceA>()
>         .uses<IServiceB, ServiceB>(); // <== Stelle 1
>
> class ServiceA : IServiceA
> {
>     private IServiceB _sb; // <== Stelle 2a
>
>     public ServiceA()
>     {
>         _sb = DIContainer.Instance.Get<IServiceB>(); // <== Stelle 2b
>     }
>
> }

Ah, ich sehe, was du meinst. Gut, dass wir drüber reden :-) Hier liegt
nämlich eines unserer Missverständnisse:

Deine Prämisse ist: "Ein Kontrakt (bzw. ein Interface) wird nur von
einer Implementation implementiert."

Das halte ich jedoch für zu eng gedacht. Selbst wenn es häufig so ist,
ist es doch nicht notwendig und immer so. Für IServiceB kann es viele
Implementationen in vielen Assemblies geben. Daher ist "Stelle 2a"
nicht ausreichend, um den Bezug zu ServiceB herzustellen.


> Kennst du das Managed Extensibility Framework (MEF) schon?http://www.codeplex.com/mef
> Vor kurzem gab es den ersten Code Drop. Ich habe ihn mir schon kurz
> angeschaut. Ich kann mir vorstellen, dass MEF für die diskutierte Idee
> nützlich ist. Muss aber noch weitere Anforschungen anstellen.

Davon hatte ich vor einiger Zeit mal was gesehen, aber da war noch
nicht soviel Doku.
Sieht jetzt schon interessanter aus. Bisher aber mehr oder weniger nur
DI ohne explizites Mapping.
Das ist noch nicht so dolle. Gerade weil die Typen noch explizit zum
CompositionContainer hinzugefügt werden müssen, finde ich den Schritt
voran noch nicht so groß. Wen schon Attribute, dann doch bitte auf das
Hinzufügen von Assemblies geschränken.

Aber egal.

Ich werde jetzt mein DI Mapping über einen Abhängigkeitsgraphen
aufbauen.
Und daraus generiere ich mir dann Abhängigkeitsdiagramme.
Da muss ich nicht auf Microsoft oder sonstwen warten :-)

Baue ich damit zuviel Infrastruktur selbst? Ne. Weil es das nicht
gibt. NDepend bietet es nicht. Und DI Container auch nicht.
Indem ich das DI Mapping über Provider generiere, mache ich mich auch
nicht von einem DI Container abhängig.
Und DRY sehe ich nicht verletzt: Zum einen meine ich ja, dass ich gar
keine Information wiederhole, zum anderen wäre eine "Denormalisierung"
aus Effizienzgründen verzeihlich. Ich bin schneller an dem Punkt, dass
ich Abhängigkeitsgraphen gezeichnet bekomme, und die
Abhängigkeitsermittlung ist schneller.

Ich werde von meinen Ergebnissen berichten.

Cheerio!

Ralf

>
> Ciao,
> Jens
>
> 2008/9/24 Ralf Westphal <i...@ralfw.de>

Jens Winter

unread,
Sep 25, 2008, 10:04:25 AM9/25/08
to altn...@googlegroups.com

Hallo Ralf,

Es tut mir leid, dass das DRY-Thema für mich noch nicht ganz abgeschlossen ist. Mir ist durchaus bewusst, dass deine Ursprungsfrage nichts damit zu tun hatte.


2008/9/25 Ralf Westphal <in...@ralfw.de>


> DIContainer.Instance
>     .Root<IServiceA, ServiceA>()
>         .uses<IServiceB, ServiceB>(); // <== Stelle 1
>
> class ServiceA : IServiceA
> {
>     private IServiceB _sb; // <== Stelle 2a
>
>     public ServiceA()
>     {
>         _sb = DIContainer.Instance.Get<IServiceB>(); // <== Stelle 2b
>     }
>
> }

Ah, ich sehe, was du meinst. Gut, dass wir drüber reden :-) Hier liegt
nämlich eines unserer Missverständnisse:

Deine Prämisse ist: "Ein Kontrakt (bzw. ein Interface) wird nur von
einer Implementation implementiert."

Das siehst du falsch. Mir ist bewusst, dass es zu einem Interface mehrere Implementationen geben kann. Und ich sehe nun auch, dass ich nicht ausreichend beschrieben habe, was ich meine.
Es ist an der Stelle 1 das "uses", was überflüssig ist. Die uses-Beziehung, die du in der Konfiguration verankerst ist bereits an den Stellen 2a und 2b im Code manifestiert. Das, und nicht das Mapping, hatte ich mit der Verletzung von DRY gemeint. (Sollte ich dich falsch verstanden haben, habe ich mich spätestens jetzt total lächerlich gemacht :) )

Ich denke mal laut:
Mir ist klar, dass du die uses-Beziehung für den den Graphen benötigst (logischerweise...). Aber für den Graphen brauchst du auch die Mapping-Beziehungen. Du benötigst also etwas, das beide Aspekte zusammenführt. Und das möchtest du gerne per fluent API machen.


> Kennst du das Managed Extensibility Framework (MEF) schon?http://www.codeplex.com/mef
> Vor kurzem gab es den ersten Code Drop. Ich habe ihn mir schon kurz
> angeschaut. Ich kann mir vorstellen, dass MEF für die diskutierte Idee
> nützlich ist. Muss aber noch weitere Anforschungen anstellen.

Davon hatte ich vor einiger Zeit mal was gesehen, aber da war noch
nicht soviel Doku.
Sieht jetzt schon interessanter aus. Bisher aber mehr oder weniger nur
DI ohne explizites Mapping.
Das ist noch nicht so dolle. Gerade weil die Typen noch explizit zum
CompositionContainer hinzugefügt werden müssen, finde ich den Schritt
voran noch nicht so groß. Wen schon Attribute, dann doch bitte auf das
Hinzufügen von Assemblies geschränken.

Ich habe das MEF für die Idee der Visualisierung noch nicht aufgegeben. Ich werde noch weiter forschen und einen Beweis liefern.
Ich weiß, dass kein explizites Mapping (oder meinst du vielleicht "explizite Konfiguration"?) möglich ist. Ich werde sehen, in wieweit das hinderlich ist.

 
Aber egal.

Ich werde jetzt mein DI Mapping über einen Abhängigkeitsgraphen
aufbauen.

Was ich mir sehr gut vorstellen kann, ist, dass man ein Tool (im Gegensatz zu "Framework" oder in die Anwendung eingebackene Infrastruktur) dafür entwickeln kann.

So kann man dann auch neben

Bitmap bmp = DependencyManager.Instance.DependencyGraph.ToBitmap();

auch so etwas machen:

DependencyManager.WriteWindsorConfigFile("MyMappings.xml");

Ciao,
Jens

Ralf Westphal

unread,
Sep 28, 2008, 12:39:53 PM9/28/08
to altnetde
So, jetzt hab ich eine Dependency Graph Komponente gebastelt:

http://code.google.com/p/componentdependencygraph/

Ist als Binary runterladbar und auch als Quellcode. Habe mich bemüht,
die Testcoverage hoch zu halten :-)

Meine Erfahrung damit bisher: funktioniert wunderbar. Statt Mapping
einfach den Abhängigkeitsgraphen definieren und das Mapping generieren
lassen. Fertig. Nix doppelt definiert. Und nicht irgendwelche DI
Container analyisieren, wie aus ihnen Abhängigkeiten extrahiert werden
könnten.

Mit Graphviz auf dem Rechner installiert generiere ich mir dann ganz
einfach daraus Abhängigkeitsdiagramme. So bequem war es noch nie. Und
ganz ohne NDepend :-) (das sowieso scheitert, wenn die statischen
Referenzen fehlen).

Enjoy!

PS: Demnächst bastle ich noch ein wenig am API. Gefällt mir noch nicht
100%. Aber die Richtung stimmt. Auch die Abhängigkeitsdiagramme dürfen
noch etwas ausgefeilter werden. Schachtelungen will ich noch einbauen.
Mal schauen... Erstmal ist der Wurf jedoch ausreichend. So kann ich
gerade in Trainings sofort sehr plastisch machen, wie die Anwendung
intern auf Architekturebene aussieht.
Reply all
Reply to author
Forward
0 new messages