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.
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.
Es geht ja nicht um Flusskontrolle. Aber wenn FooLoader.Load null zurückgibt, was sagt es dir über die Ursache, warum das Laden fehlgeschlagen ist? Zugriffsrechte? Fehlendes Verzeichnis? Kosmische Strahlung?
Das bedeutet ja nicht, dass man nicht mal kurz vor dem Aufruf von FooLoader.Load checken kann, ob die Datei besteht etc.
internal class FooSaver
{
internal bool Save(Foo foo, string targetPath)
{
// tatsächlich speichern und return true wenn fertig ohne fehler
}
}
internal class FooLoader
{
internal Foo Load(string sourcePath)
{
// tatsächliches laden, null wenn nicht möglich
}
}
Beide Methoden sollten meiner Meinung nach eine Exception werfen, wenn etwas schiefgeht, nicht null oder false. Sonst muss die aufrufende Methode die Rückgabewerte kontrollieren usw. Das ist mit Exceptions deutlich übersichtlicher.
Ich habe mir mittlerweile angeeignet, dass Argumente und Rückgabewerte standardmäßig nie null sein können. Die wenigen Ausnahmen, welche idealerweise auf private Methoden beschränkt sein sollten, müssen entsprechend eindeutig dokumentiert sein.
An sich finde ich die Verwendung von Streams bei so etwas eine gute Sache, eben weil sie so abstrakt sind. Eine Überladung mit Pfaden kann man immer noch anbieten. Wenn du das nicht möchtest - wie wärs mit so etwas:
interface IStreamProvider {
Stream OpenRead(string name);
Stream OpenWrite(string name);
void Close(Stream stream);
}
class FileStreamProvider : IStreamProvider {
public Stream OpenRead(string name){ return File.OpenRead(name); }
public Stream OpenWrite(string name){ return File.OpenRead(name); }
public void Close(Stream stream){ stream.Close(); }
}
class MemoryStreamProvider : IStreamProvider {
private MemoryStream stream;
public MemoryStreamProvider(MemoryStream stream){
this.stream = stream;
}
public MemoryStreamProvider():this(new MemoryStream()){
}
public MemoryStream Stream{get {return stream;}}
public Stream OpenRead(string name){ return stream; }
public Stream OpenWrite(string name){ return stream; }
public void Close(Stream stream) { /* nichts schließen */ }
}
class FooManager {
private IStreamProvider streamProvider;
public FooManager(IStreamProvider streamProvider){
this.streamProvider = streamProvider;
}
public void Save(Foo foo, string path){
var stream = streamProvider.OpenWrite(path);
try {
new FooSaver().Save(foo, stream);
} finally {
streamProvider.Close(stream);
}
}
public Foo Load(string path){
var stream = streamProvider.OpenRead(path);
try {
return new FooSaver().Load(stream);
} finally {
streamProvider.Close(stream);
}
}
}
Dann hast du nach außen hin noch immer Pfadangaben, kannst aber für die Tests einen MemoryStreamPovider verwenden, um zu überprüfen, was geschrieben wird.
Welches Load/Save willst du denn testen? Das vom Manager oder das von FooLoader/FooSaver.
Allgemein wäre es evtl. ganz nützlich, wenn du statt eines Dateinamens einen Stream verwendest, dann kanst du leicht auch besagten MemoryStream übergeben.
Zitat
Nur dann habe ich den Fall nicht abgedeckt das die Datei wegen fehlenden Rechten nicht gespeichert werden konnte, der Benutzer wählt das Zielverzeichnis aus, und wenn er unter Vista/Seven "Programme" aus wählt muss ich das ja auch entsprechend handeln.
Auf jeden fall kann ich das, so denk ich, in zwei Teile Splitten.
Das Problem wird doch eher sein, dass die Methode, die die Datei schreibt, eine solche Exception gar nicht abhandeln kann sondern sie einfach nach oben durchreicht.
Vor allem ist das erste etwas sinnbefreit, da der Formatstring gar keinen Platzhalter mehr enthält ({0}), trotzdem aber ein Wert dafür übergeben wird. Dann kann man auch direkt ein normales String-Literal verwenden.
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.
Wer sich an der Werbung stört, kann diese doch recht problemlos mit den passenden technischen Mittelchen ausblenden.
Nur am Rande zum Thema Bankkonte: ich rate dringend davon ab, eine Bankverbindung auf die Seite zu setzen - ich habe etliche Male eingezogene Beträge zurückholen und mich mit den geschädigten Anbietern herumschlagen müssen, weil irgendwelche Spaßvögel meinten, die Daten missbrauchen zu müssen.
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.
Zitat
Wir haben weder MVC noch eine nachrichtenorientierte Architektur, sondern eine verteilte Anwendung ähnlich Rainbirds Applikationsserver, 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.
Zitat
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!
Zitat
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).
Zitat
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.
Zitat
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.
Zitat
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.
Zitat
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
Zitat
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).
Danke für deine Antwort. Ich werde also wohl die Invarianten verwenden. Der IL rewriter scheint bei automatischen Properties übrigens relativ clever zu sein.
Aus
internal class Stream
{
...
public Type Type
{
get;set;
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(Type != null);
}
}
Macht er (sinngemäß):
internal class Stream
{
..
public Type Type
{
get {
Contracts.Ensure(Contracts.Result<Type>() != null);
return type;
}
set {
Contracts.Require(value != null);
type = value;
}
}
...
}
Das heißt, anstatt alle Invarianten am Ende des getters und setters zu überprüfen, wird aus den Invarianten passende Pre- und Postconditions für die Property erzeugt, da bei den automatischen Properties eh nur ein Backingfield betroffen ist.
Ich spiele gerade ein bisschen mit Code Contracts herum und habe ein kleines Problem mit der statischen Analyse. Gegeben folgende Klasse:
internal class Stream
{
private Type type;
public Stream(Type type)
{
Contract.Requires(type != null);
this.type = type;
}
public Type Type
{
get
{
Contract.Ensures(Contract.Result<Type>() != null);
return type;
}
set
{
Contract.Requires(value != null);
type = value;
}
}
}
Meinem Verständnis nach kann type nie null werden. Trotzdem beschwert sich die statische Codeanalyse bei "return type":
Damit kann die Postcondition dann statisch verifiziert werden.
Kann mich jemand erhellen, warum der Analyzer das ohne Invariante nicht verifizieren kann? Ist das ein grundsätzliches Problem des Analyzers oder by-design, oder übersehe ich etwas im Code?
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.
Zitat
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:
Zitat von mir
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 ( http://www.google.de/search?q=inject+ser...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.
Zwei Sachen fallen mir auf:
1. frm.GetType() == frm.GetType() ist immer true. Was willst du hier erreichen?
2. Wenn MdiChildren nicht leer ist, wird die Methode immer nach dem ersten Schleifendurchlauf verlassen dank des return Statements.
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:
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();
}
}
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.
Zitat
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.
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:
Zitat
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);
Zitat
2. 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.
Zitat
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.
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.
Zitat
4. 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.
Zunächst einmal gilt ja folgende Überlegung: wenn MoveNext() false zurückgibt, kann die gesamte Enumeration abgebrochen werden, also:
public static IEnumerable<T> SelectDistinctConsecutive<T>(this IEnumerable<T> items)
{
using (IEnumerator<T> enumerator = items.GetEnumerator())
{
bool furtherValues = enumerator.MoveNext();
while (furtherValues)
{
T lastValue = enumerator.Current;
yield return lastValue;
do
{
furtherValues = enumerator.MoveNext();
// neu:
if(!furtherValues)
{
yield break;
}
} while (enumerator.Current.Equals(lastValue));
}
}
}
Jetzt besteht das Problem, dass das erste while(furtherValues) ab dem zweiten Durchlauf nicht mehr false sein kann, da sonst die Enumeration bereits beendet worden wäre. Also probieren wir:
public static IEnumerable<T> SelectDistinctConsecutive<T>(this IEnumerable<T> items)
{
using (IEnumerator<T> enumerator = items.GetEnumerator())
{
bool furtherValues = enumerator.MoveNext();
while (true)
{
T lastValue = enumerator.Current;
yield return lastValue;
do
{
furtherValues = enumerator.MoveNext();
if(!furtherValues)
{
yield break;
}
} while (enumerator.Current.Equals(lastValue));
}
}
}
Das kann allerdings nicht mit leeren Enumerationen umgehen, also prüfen wir, dass zumindest ein Element verfügbar ist:
public static IEnumerable<T> SelectDistinctConsecutive<T>(this IEnumerable<T> items)
{
using (IEnumerator<T> enumerator = items.GetEnumerator())
{
bool furtherValues = enumerator.MoveNext();
if (furtherValues)
{
while (true)
{
T lastValue = enumerator.Current;
yield return lastValue;
do
{
furtherValues = enumerator.MoveNext();
if (!furtherValues)
{
yield break;
}
}
while (enumerator.Current.Equals(lastValue));
}
}
}
}
Jetzt läuft es wieder, aber die Verwendung der furtherValues Variable hat kaum noch Sinn, also wird sie geinlined:
public static IEnumerable<T> SelectDistinctConsecutive<T>(this IEnumerable<T> items)
{
using (IEnumerator<T> enumerator = items.GetEnumerator())
{
if (enumerator.MoveNext())
{
while (true)
{
T lastValue = enumerator.Current;
yield return lastValue;
do
{
if (!enumerator.MoveNext())
{
yield break;
}
}
while (enumerator.Current.Equals(lastValue));
}
}
}
}
Das ganze ist jetzt leider durch die doppelte Schleife stark geschachtelt, weswegen ich wieder auf meinen Vorschlag mit der einfachen Schleife verweise. Die Idee dahinter: entscheide pro Element, ob es zurückgegeben wird. Die Kondition für das erste Element ist der erfolgreiche Aufruf von MoveNext, die Kondition für alle weiteren ist die Ungleichheit zum Vorgänger. Prüfe nach jedem Element, ob es weitere gibt, ansonsten beende die Enumeration.
IIRC exportiert das aber eben nur den Codestyle. So Sachen wie templates exportiert die Funktion nicht. Templates kannst du über den Template-Explorer exportieren.
Du kannst natürlich auch einfach alles sichern/übertragen, indem du AppData\Roaming\JetBrains\ReSharper\... auf den Zielrechner kopierst.
Es gibt auch ein "Resharper Settings Manager" Plugin (http://rsm.codeplex.com/), das habe ich aber noch nicht getestet.
es werden zwei verschiedene Bedingungen geprüft. Einmal nur furtherValues und einmal furtherValues und die Gleichheit zweier aufeinanderfolgender Elemente.
Beide Prüfungen werden direkt nacheinander ausgeführt, falls furtherValues false wird oder sobald das aktuelle Element ungleich des vorherigen ist. In beiden Prüfungen wird der Wahrheitsgehalt von furtherValues überprüft, auch wenn sich dieser zwischen den beiden Prüfungen nicht ändern kann. Ich sage ja nicht, dass das dramatisch ist und ein System zur Laufzeit zu Fall bringt, es ist nur nicht optimal.
herbivores letzte Version ist tatsächlich ziemlich kurz. Einige Details sind aber nicht optimal: z.B. wird furtherValues bei jedem Schleifendurchlauf 2x hintereinander geprüft, obwohl sich der Wert dazwischen nicht ändern kann: einmal im letzten while(...) und direkt danach (falls die Werte ungleich sind) im ersten while(furtherValues). Auch finde ich die geschachtelte Schleife etwas kompliziert zu verstehen.
Die erste Version von Uwe81 finde ich sehr leicht zu verstehen, wobei das if auch den while-Block umschließen könnte, da die Bedingung im while nie wahr wird, wenn das erste MoveNext() false zurückgibt.
Man kann natürlich auch die Schachtelung reduzieren und dadurch die verschiedenen Fälle (leere Enumeration, erstes Element, Rest) auch noch explizit in der Struktur wiedergeben:
public static IEnumerable<T> DistinctConsecutive<T>(this IEnumerable<T> items)
{
using (var enumerator = items.GetEnumerator())
{
// leere Enumeration abhandeln
if (!enumerator.MoveNext())
{
yield break;
}
// erstes Element abhandeln
var previous = enumerator.Current;
yield return previous;
// Rest
while (enumerator.MoveNext())
{
var current = enumerator.Current;
if (!current.Equals(previous))
{
yield return current;
previous = current;
}
}
}
}
Nicht, dass das notgedrungen besser ist als die Version von Uwe81, es wird nur einiges expliziter gemacht.
Und hier noch eine Version, die NICHT das erste Element speziell behandelt (zumindest nicht explizit):
public static IEnumerable<T> DistinctConsecutive<T>(this IEnumerable<T> items)
{
using (var enumerator = items.GetEnumerator())
{
var returnCurrent = enumerator.MoveNext();
var lastReturnedValue = default(T);
while(true)
{
if (returnCurrent)
{
lastReturnedValue = enumerator.Current;
yield return lastReturnedValue;
}
if (!enumerator.MoveNext())
{
yield break;
}
returnCurrent = !lastReturnedValue.Equals(enumerator.Current);
}
}
}
Einziger Wermutstropfen hier: MoveNext wird für leere Enumerationen 2x hintereinander aufgerufen, beide mit false als Ergebnis. Das ist zwar erlaubt, aber nicht 100% elegant.
Aber wie gesagt, die erste Version von Uwe81 finde ich gut.
Kann das sein, dass du da.Fill (= Daten laden) machen möchtest statt da.Update (= Daten in DB zurückschreiben)?
[edit]Ah, sorry, du möchtest aktualisieren schreibst du. Hast du denn ein InsertCommand angegeben? Lädst du vorher die Daten in das Dataset, damit es das Schema kennt?[/edit]
Es gibt keinen Namespace System.Data.SQLite.Linq, ganz einfach. Nur weil die Assembly so heißt, muss es keinen gleichnamigen Namespace geben. Schau dir mein Beispiel ein, du benötigst lediglich den System.Data.Linq Namespace für die Linq-Abfragen.
Also, Provider installieren, Datenverbindung anlegen, Model erzeugen und wie gewohnt verwenden:
namespace ConsoleApplication3
{
using System;
using System.Linq;
internal class Program
{
private static void Main(string[] args)
{
using (var context = new SQLiteTestConnection())
{
var users = from user in context.Users
where user.Name == "Andre"
select user;
foreach (var user in users)
{
Console.Out.WriteLine("users.ID = {0}", user.ID);
}
}
}
}
}
Hat von euch einer ne Idee oder am besten sogar eine kleine(detailllierte) Anleitung?
Hallo und willkommen,
Wenn du uns jetzt noch eine detaillierte Fehlerbeschreibung mitteilst, könnten wir dir vielleicht helfen. Sortiere noch einmal deine Gedanken und lies dir folgendes durch: [Hinweis] Wie poste ich richtig?