Archiv der Kategorie: .NET Framework

Microservices don’t like thinking in conventional entities

I recently had one of these special AHA-moments in a workshop held by Udi Dahan, CEO of Particular. In his example he was using the classic entity of a customer.

To realise microservices means cutting functionality in a clean way and to transfer it into separate pillars (or silos). Each silo has to have the responsibility over it’s own data, on which it builds the respective business processes. So far, so good. But how can we realise this for our typical customer, who’s model is shown in the screenshot? Different properties are needed or changed by different microservices.

If the same entity is used in all pillars, there needs to be a respective synchronisation between all microservices. This results in a considerable impact on scalability and performance. In an application with lots of parallel changes of an entitiy the failing of the business processes will increase – or lead to inconsistencies in the worst case.

Conventional Customer

conventional entity of a customer

Udi suggests the following modelling

New Customer

Customer is modelled with independent entities

To identify which data belongs together, Udi suggests an interesting approach:

Ask the operating department if changing a property has a consequence for another property.

Would changing the last name of customer have an influence on the price calculation? Or on the way of marketing?

Now we need to solve the problem of aggregation, for example if I want to show different data from different microservices in my view. In a classic approach we would have a table with following columns:

ID_Customer ID_MasterData ID_Marketing ID_Pricing

This leads to the following two problems:

  1. The table needs to be extended if a new microservice is added
  2. If a microservice covers the same functionality in the form of data, you would have to add multiple columns for each microservice and allow NULL values as well

An example for the second point could be a microservice which covers the matter of payment methods. In the beginning you could only use credit cards and debit charges. Then Paypal. Bitcoin soon after. The microservice would have different tables on which the respective data for the payment method would be stored. In the aggregated table shown above it would be necessary to fill a separate column for each payment method the customer is using. If he doesn’t use it, a NULL value would be written. As you can see: This sucks.

Another approach is much more convenient for this. Which one this is and how it’s realised technically you can find on the GitHub repository of Particular.

 

Microservices mögen kein Denken in klassischen Entitäten

Einen ganz besonderen AHA Moment hatte ich kürzlich in einem Workshop bei Udi Dahan, CEO von Particular. In seinem Beispiel ging es um die klassische Entität eines Kunden.

Microservices zu realisieren bedeutet Fachlichkeiten sauber schneiden und in eigenständige Silos (oder Säulen) packen zu müssen. Jedes Silo muss dabei die Hohheit über die eigenen Daten besitzen, auf denen es die zugehörigen Geschäftsprozesse abbildet. Soweit so gut. Doch wie lässt sich dies im Falle eines Kunden bewerkstelligen, der klassischerweise wie im Screenshot zu sehen modelliert ist? Unterschiedliche Eigenschaften werden von unterschiedlichen Microservices benötigt bzw. verändert.

Wird die gleiche Entität in allen Silos verwendet, muss es eine entsprechende Synchronisierung zw. den Microservices geben. Das hat erhelbiche Auswirkungen auf Skalierbarkeit und Performance. In einer Applikation mit häufig parallelen Änderungen an einer Entität wird das Fehlschlagen von Geschäftsprozessen zunehmen – oder im schlimmsten Fall zu Inkonsistenzen führen.

Klassische Kundeentität

Klassische Kundeentität

 

Udi schlägt die folgende Modellierung vor:

Neue Modellierung eines Kunden

Der Kunde wird durch unabhängige Entitäten modelliert

Zur Identifikation, welche Daten zusammengehören, schlägt Udi einen Interessanten Ansatz vor:

Fragt die Fachabteilung, ob das Ändern einer Eigenschaft Auswirkung auf eine andere Eigenschaft hat. 

Würde das Ändern des Nachnamens einen Einfluss auf die Preiskalkulation haben? Oder auf die Art der Marketings?

Nun gilt es noch das Problem der Aggregation zu lösen, sprich wenn ich in meiner Anzeige unterschiedliche Daten unterschiedlicher Microserivces anzeigen möchte. Klassischerweise würde es jetzt eine Tabelle geben, die die Spalten

 

ID_Kunde ID_Kundenstamm ID_Bestandskundenmarketing ID_Preiskalkulation

 

besitzt. Das führt aber zu 2 Problemen:

  1. Die Tabelle muss immer erweitert werden, wenn ein neuer Microservices hinzugefügt wird.
  2. Sofern ein Microservices die gleiche Funktionalität in Form unterschiedlicher Daten abdeckt, müssten pro Microservices mehrere Spalten hinzugefügt und NULL Werte zugelassen werden.

Ein Beispiel für Punkt 2 wäre ein Microservices, der das Thema Bezahlmethoden abdeckt. Anfangs gab es beispielsweise nur Kreditkarte und Kontoeinzug. Dann folgte Paypal. Und kurze Zeit später dann Bitcoin. Der Microservices hätte hierzu mehrere Tabellen, wo er die individuelle Daten für die jeweilige Bezahlmethode halten würde. In oben gezeigter Aggregationstabelle müsste aber für jede Bezahlmethode, die der Kunde nutzt, eine Spalte gefüllt werden. Wenn er sie nicht benutzt, würde NULL geschrieben werden. Man merkt schon: Das stinkt.

Ein anderer Ansatz ist da deutlich besser geeignet. Welcher das ist und wie man diesen technischen realisieren kann, könnt ihr im GitHub Repository von Particular nachschauen.

 

Quellen zu Defensives Design und Separation of Concerns sind jetzt online

Den Quellcode zu meinen Vorträgen auf den Karlsruher Entwicklertagen und der DWX sind jetzt online:

  • Zu Super Mario Kata mit Fokus auf Defensivem Design geht es hier.
  • Zur Prüfsummen Kata mit fokus auf Separation of Concerns gelangt ihr hier.

Auf beiden Seiten findet ihr auch die Links zu den PowerPoint Folien. Im Juli 2018 werde ich den Code der Prüfsummen Kata noch in Form von Iterationen veröffentlichen und beide Talks als YouTube Video freigeben.

IMG_20180627_145405

Defensives Design Talk auf der DWX

Ihr wollt Teile davon in euren Vorträgen verwenden, ihr wollt ein Training zu dem Thema oder ich soll dazu in euerer Community einen Vortrag halten, dann kontaktiert mich über die auf GitHub genannten Kanäle.

Open Closed Principle mit 2 Zeilen Code

In meinem Video zu 60 Minuten mit der Pfadfinderregel war ich noch die Lösung zu Castle.Windsor schuldig. In diesem Webcast zeige ich wie die CollectionResolver-Funktionalität genutzt werden kann, um nachträglich hinzugefügte Implementierungen automatisch im Container zu registrieren.

Dadurch kann das OCP vollständig eingehalten werden, da lediglich eine neue Klasse hinzugefügt werden und der restliche Code nicht angefasst werden muss. Den Code habe ich hier zur Verfügung gestellt. Fragen und Feedback einfach in die Kommentare.

Durch Klicken auf das Bild geht es zum Video

Durch Klicken auf das Bild geht es zum Video

Die großen 4: Pfadfinderregel, Wirtschaftlichkeit, Clean Code, SOLID Principles

Was ist damit gemeint? Gemäß der Pfadfinderregel soll ein Entwickler Code immer besser hinterlassen, als er ihn vorgefunden hat. Clean Code oder guter Code ist häufig dann erreicht, wenn das Mindestmaß an essentiellen Code Prinzipien umgesetzt ist. Das sind die sogenannten SOLID Principles. Jedoch ist guter Code kein Selbstzweck, sondern dient dem größeren Ziel der Wirtschaftlichkeit.

Im folgenden Video zeige ich an einem Praxisbeispiel, wie ich bei einem bestehendem, eher unwichtigem Projekt vorgegangen bin. Timeboxed in 1h so viel refaktorisieren und den Code verbessern wie möglich. Dabei gehe ich auf Prinzipien wie DRY und OCP ein und zeige Techniken wie DI, sowie Tools wie den IoC Container Castle Windsor.

Feedback nehme ich wie immer gerne mit. Wenn ihr mehr von solchen Videos sehen wollt, schreibt mir das in die Kommentare, damit ich weiß: Hier lohnt es sich mehr zu machen.

Zum YouTube Video

Durch Klicken auf das Bild geht es zum Video

Build Server endlich ohne DevExpress

DevExpress ist leider alles andere als vorbildlich, was den Einsatz auf Build Servern angeht. Weder bietet das Unternehmen NuGet Packages, noch lässt sich die Lizenzprüfung auf dem Build Server deaktivieren.

Mit einem kleinen Trick lässt sich aber die Installation umgehen. Hierzu ruft ihr den Installer auf dem Build Server/Agent auf, loggt euch mit euren Daten an dem unten aufgeführten Screenshot ein und brecht nach erfolgreichem Login ab einfach ab.

DevExpress Login Dialog

Damit wurden mehrere Registry Einträge geschrieben, die leider maschinenspezifisch sind. Diese findet ihr hier:

  • HKLM\SOFTWARE\Classes\Licenses378852D-D597-4A32-B6D9-680A16A3CDA6\***
  • HKLM\SOFTWARE\Classes\Licenses\6F0F8269-1516-44C6-BD30-0E90BE27871C\***

Wenn ihr in den Projekten dann DLL-Referenzen aus einem Repository-Verzeichnis verwendet oder euch eigene NuGet Packages baut, benötigt ihr in Zukunft keine Installation mehr. Das gilt auch für Developer, die nicht an der UI mitentwickeln.

Wohin mit den Queries, Repositories und UnitOfWorks in meinen Anwendungsschichten

Am vergangenen .NET Open Space gab es eine Session namens „Repositories oder ORM“. Den Titel fand ich ein wenig unglücklich gewählt. Nutzt man nämlich einen OR-Mapper, so verwendet man implizit Repositories. Zumindest ist das beim Entity Framework der Fall. Dort entspricht der DbContext nämlich der UnitOfWork und die darauf implementierte generische Set-Methode liefert die Repositories zu allen Datenbank-Entitäten.

Grob gesagt sollte pro Geschäftsvorfall (aus Sicht der UI) ein DbContext erzeugt, darauf alle Änderungen und Abfragen ausgeführt und im Anschluss per Commit in einer Transaktion ausgeführt werden. Die Transaktion findet implizit statt, sodass ihr sicher sein könnte, dass entweder alles in die Datenbank geschrieben wurde oder nichts.

Nun wurde die Frage gestellt wie mit Abfragen zu Verfahren ist: Sollte man in allen Layern auf die Repositories zugreifen und dann per LINQ beliebig filtern? Ich bin der Meinung, dass man dies tunlichst unterlassen sollte. Stattdessen nutze ich in der Regel eine der zwei Möglichkeiten (welche sich beide gut zum Testen eigenen):

  • Eine dedizierte Repository-Implementierung, die intern die Abfragen kapselt. Das könnte zum Beispiel die Klasse ‚Rechnungen‘ mit der Methode ‚AlleMitDatumGrößerAls(DateTime datum)‘  sein. Idealerweise mappt das Repository ‚Rechnungen‘ dabei die Entitäten auf Business Objekte. AutoMapper kann in dem Fall viel Schreibarbeit abnehmen.
  • Sollten mehrere Repositories die gleiche Abfrage verwenden, so bietet es sich an die Query in eine eigene Klasse dafür zu extrahieren. Zum Beispiel die Klasse ‚FindeBenutzerMitId‘, welche im Konstruktor die Id übergeben bekommt. Auf der UnitOfWork lässt sich dann die Query z.B. in der Form ausführen: ‚uow.ExecuteQuery(new FindeBenutzerMitId(1))‘. Dafür muss allerdings erst die UnitOfWork um einen solchen Mechanismus erweitert werden. Ich hatte diesen Ansatz vor ein paar Jahren in diesem Video beschrieben.

Leider ist ein Blog Beitrag ungeeignet um ein solch komplexes Thema in der notwendigen Tiefe zu besprechen. Ziel war es daher nur Denkanstöße zu geben und eine Diskussion zu starten. Wer nützliche Ressourcen zu dem Thema hat oder wer andere Ansätze verfolgt, kann diese gerne in die Kommentare schreiben.

Paket vs. NuGet

Am vergangenen .NET Open Space haben einige Teilnehmer nach Erfahrungen zu Paket, der mehr oder weniger neuen Alternative zu NuGet. Mein Statement möchte ich an dieser Stelle für alle festhalten:

Wir haben unser ERP-System mit ca. 70 Projekten vor über 6 Monaten umgestellt und sind sehr zufrieden damit. Warnen muss ich lediglich vor 3 Punkten, die einem bewusst sein müssen:

  • Paket ist nicht in Visual Studio integriert und muss daher über die Kommandozeile ausgeführt werden. Eine Integration ist auch nicht geplant. (Aktualisiert: Ein entsprechendes GitHub Projekt steht zur Verfügung)
  • Jedes NuGet Package, welches die install.ps1 aufruft, funktioniert ggf. nicht richtig nach der Installation mit Paket. Das ist z.B. bei PostSharp der Fall, was ich hier beschrieben habe. Der geneigte Leser möge bitte bei PostSharp für das offene Issue dazu voten.
  • NET Core Projekte erlauben keine Assembly Referenzen mehr, sodass Paket nicht verwendet werden kann.

Die Gründe für einen Abgang von NuGet sind vielfältig und wurden vom Entwicklerteam selbst beschrieben. Mit annähernd allen Problemen räumt Paket auf. Um nur zwei davon zu nennen:

  • Mit dem Updaten von packages werden die Projektdateien nicht mehr verändert. Stattdessen werden lediglich 2 Dateien von Paket selbst aktualisiert. Damit gehören Merge-Konflikte der Vergangenheit an.
  • Einen Abhängigkeitsgraphen bekommt man ebenfalls mitgeliefert. Daraus lässt sich schnell schließen welches Package ein anderes in welcher Version referenziert.

Eine Frage beim Open Space war, ob sich damit auch das gleiche Package in unterschiedlichen Versionen einbinden lässt. Nein, das ist nicht der Fall, was aber nichts mit NuGet oder Paket zu tun hat. Das ist der Tatsache geschuldet, dass ein .NET Prozess eine Assembly nur in genau einer Version laden kann. Zum Lösen dieses Problems bedarf es also immer Binding Redirects. In NuGet 3 soll zumindest ein Feature zum einfachen Konsolidieren unterschiedlicher Versionen eingebaut sein.

Mit NuGet 3 soll ohnehin ein großes Redesign stattfinden, sodass ein näherer Blick darauf ratsam ist. Im Team Blog finden sich einige nützliche Ressourcen.

Ich wollte den Blog Post kurz halten, deshalb habe ich nicht alle Vorteile aufgezählt. Wenn aber ein Leser der Meinung ist, dass noch etwas unbedingt genannt werden soll, dann einfach in die Kommentare posten.

How to get the PostSharp package running with Paket

Recently, I reported some difficulties that I‘ve experienced with the PostSharp NuGet package when I was using the new package manager Paket. I tracked the problem down to the ‚install.ps1‘ script which is executed by NuGet after adding PostSharp to a project. This PowerShell script (located under packages\PostSharp\tools) patches your project file. Since this behaviour is already proclaimed from the pulpit by NuGet, I hope that PostSharp is going to remove that asap. Until then, you can execute the script manually or patch the csproj-File by yourself.

In order to do that, insert the following 2 snippets at the the position shown in the screenshots:

   1: <DontImportPostSharp>True</DontImportPostSharp>

 

image

 

Don’t forget to change the relative path to your PostSharp package in the following snippet:

   1: <Import Project="..\..\packages\PostSharp\tools\PostSharp.targets" Condition="Exists('..\..\packages\PostSharp\tools\PostSharp.targets')" /> 

   2: <Target Name="EnsurePostSharpImported" BeforeTargets="BeforeBuild" Condition="'$(PostSharp30Imported)' == ''">

   3: <Error Condition="!Exists('..\..\packages\PostSharp\tools\PostSharp.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://www.postsharp.net/links/nuget-restore." />

   4: <Error Condition="Exists('..\..\packages\PostSharp\tools\PostSharp.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://www.postsharp.net/links/nuget-restore." />

 

image

 

I also recommend you to activate the following PostSharp Option in Visual Studio:

image

Interview mit Daniel Marbach mitgestalten

Am 18. März wird mir Daniel Marbach ein Video Interview zu dem BDD-Framework Machine.Specifications geben. Ein kleiner Vorgeschmack gefällig?

Wodurch hebt sich MSpec von anderen BDD-Frameworks ab?

oder

Welche Features sind gerade in der Pipeline?

oder

Welche anderen Frameworks ergänzen MSpec gut?

Das Video veröffentliche ich dann auf meinem YouTube Channel. Ihr könnt die Richtung des Gesprächs mitgestalten, indem ihr mir rechtzeitig eure Fragen in die Kommentare postet.

Konsequente Objektorientierung – Die besseren Methodenparameter

Kürzlich habe ich eine Lösung zugeschickt bekommen, die folgende Methode enthielt.

   1: public bool compare(string item1, string item2)

   2: {

   3:     if (item1 == null || item2 == null)

   4:         return false;

   5:  

   6:     //more code

   7: }

 

In diesem Webcast möchte ich einen Ansatz zeigen, den ich als gute Alternative zu obigem Code sehe. Dabei setze ich konsequent auf Objektorientierung zur Trennung der Aspekte.

Testen von Datenbankabfragen – Es geht auch einfach

In diesem Screencast zeige ich wie selbst Abfragen mit mehreren Bedingungen und Sortierungen einfach getestet werden können. Unter der Haube kommt das Entity Framework zum Einsatz. Davon kriegt man dank Dependency Injection aber nichts mit.

Die erwähnte Webcast Serie, sowie weiteren Beispielcode und das Projekt auf Github gibt es hier. Feedback oder Fragen nehme ich gerne entgegen. Wenn ihr wollt, dass ich die Serie fortsetze, sprecht mich über einen Kanal eurer Wahl an.

Bessere Enumerations in NET

Ist euch der Smell von typischen Enumerations auch schon in die Nase gestiegen? In diesem Webcast zeige ich eine Alternative zu den typischen Enums in NET. Damit können das Open Closed Principle und Separation of Concerns besser umgesetzt werden, was meiner Meinung nach zu einer höheren Kohäsion führt.

 

Weiterführende Links:

 

Wie haltet ihr es mit Enums? Arbeitet ihr schon auf diese Weise? Wollt ihr mehr Webcasts zu Clean Code?

Flexibles Bootstrapping durch Composition

In diesem Webcast zeige ich unseren Kompositions-Ansatz für das Bootstrapping. Die Interfaces sind schmal, die Implementierungen überschaubar, das Ganze ist flexibel und lässt sich gut testen.

Hier der gezeigte Code:

   1: public class BootstrapperContext

   2: {

   3:     public BootstrapperContext()

   4:     {

   5:         AppConfig = new ApplicationConfig();

   6:         Container = new WindsorContainer();

   7:     }

   8:  

   9:     public IWindsorContainer Container { get; private set; }

  10:     public ApplicationConfig AppConfig { get; private set; }

  11: }

   1: public interface IAmABootstrapperAction

   2: {

   3:     void Execute(BootstrapperContext context);

   4: }

 

   1: public interface IAmABootstrapperComposition

   2: {

   3:     IEnumerable<IAmABootstrapperAction> Actions { get; }

   4: }

 

   1: public class BootstrapperExecutor

   2: {

   3:     public static void StartupApplication(IAmABootstrapperComposition bootstrapperComposition)

   4:     {

   5:         var exceptionMessage = "Beim Starten der Anwendung ist ein Fehler aufgetreten. Bitte den Support kontaktieren.\n\n";

   6:  

   7:         if (bootstrapperComposition.Actions == null || !bootstrapperComposition.Actions.Any())

   8:         {

   9:             throw new BootstrapperException(exceptionMessage, new ArgumentOutOfRangeException("Auf dem Bootstrapper waren keine Actions definiert"));

  10:         }

  11:  

  12:         var context = new BootstrapperContext();

  13:  

  14:         var time = TimedAction.Run(() =>

  15:                                    {

  16:                                        foreach (var action in bootstrapperComposition.Actions)

  17:                                        {

  18:                                            var actionName = action.GetType().Name;

  19:                                            SiAuto.Main.LogMessage(string.Format("{0} gestartet", actionName));

  20:  

  21:                                            var timeTaken = TimedAction.Run(() => { ExecuteAction(action, context, exceptionMessage); });

  22:  

  23:                                            SiAuto.Main.LogMessage(string.Format("{0} in {1} erfolgreich durchgeführt",

  24:                                                                                 actionName,

  25:                                                                                 timeTaken.Format())

  26:                                                );

  27:                                        }

  28:                                    });

  29:     }

  30:  

  31:     private static void ExecuteAction(IAmABootstrapperAction action, BootstrapperContext context, string exceptionMessage)

  32:     {

  33:         try

  34:         {

  35:             action.Execute(context);

  36:         }

  37:         catch (Exception ex)

  38:         {

  39:             SiAuto.Main.LogException(string.Format("Fehler beim Bootstrapping: {0}", ex.GetFullExceptionMessage()), ex);

  40:             throw new BootstrapperException(string.Format("{0}{1}", exceptionMessage, ex.GetFullExceptionMessage()), ex);

  41:         }

  42:     }

  43: }

 

Welchen Ansatz verfolgt ihr?

Fragen oder Feedback könnt ihr mir gerne als Kommentar hinterlassen.

Ready-to-Use C# Enumeration Extension Methods

Bei dem unten stehenden Code handelt es sich um fertige Enum Extension Methods in C#. Damit lassen sich unter anderem

  • die Enum Description
  • der Enum Comment
  • das Enum Value
  • alle Enum Values und
  • gesetzte Enum Flags

auslesen. Der Code ist bereits ein wenig in die Jahre gekommen und einige Methoden habe ich lediglich noch aus Kompatiblitätsgründen drin. Die Tests sind mit Machine.Specifications 0.6.2 geschrieben.

Kopiert euch einfach, was ihr benötigt und passt es nach Belieben an. Über Likes oder Comments, wenn euch der Code Arbeit gespart hat, wäre ich dankbar.

Über gute Alternativen zu Enums könnt ihr hier etwas lesen.

 

Implementierung:

public static class EnumerationExtensions

{

    public static bool ByteIsSet<TEnum>(this Enum bytes, TEnum enumValue)

    {

        if (!Attribute.IsDefined(typeof (TEnum), typeof (FlagsAttribute)) ||

            bytes.GetType() != enumValue.GetType() ||

            bytes.GetType() != typeof (TEnum))

        {

            return false;

        }

 

        return ((Convert.ToInt32(bytes) & Convert.ToInt32(enumValue)) == Convert.ToInt32(enumValue));

    }

 

    public static IList<EnumEntry> GetEnumerationEntries(this Type enumeration)

    {

        var values = Enum.GetValues(enumeration);

 

        var result = new List<EnumEntry>();

 

        foreach (var value in values)

        {

            // ReSharper disable ExpressionIsAlwaysNull

            var desc = (value as Enum).GetEnumDescription();

            var comment = (value as Enum).GetEnumComment();

            // ReSharper restore ExpressionIsAlwaysNull

 

            var name = Enum.GetName(enumeration, value);

            var id = (int)value;

 

            result.Add(new EnumEntry

                       {

                           Name = name,

                           Id = id,

                           DisplayName = desc,

                           Comment = comment

                       });

        }

 

        return result;

    }

 

    public static string GetEnumDescriptionIfExists(this Enum enumeration)

    {

        return GetEnumDescription(enumeration, true);

    }

 

    public static string GetEnumDescription(this Enum enumeration)

    {

        return GetEnumDescription(enumeration, false);

    }

 

    private static string GetEnumDescription(Enum enumeration, bool nullIfNotExists)

    {

        var fi = enumeration.GetType().GetField(enumeration.ToString());

 

        if (fi == null)

        {

            return nullIfNotExists ? null : string.Empty;

        }

 

 

        var attribute = (DescriptionAttribute[])fi.GetCustomAttributes(typeof (DescriptionAttribute), false);

 

        if (attribute.Length > 0)

        {

            return attribute[0].Description;

        }

 

 

        return nullIfNotExists ? null : enumeration.ToString();

    }

 

    public static string GetEnumCommentIfExists(this Enum enumeration)

    {

        return GetEnumComment(enumeration, true);

    }

 

    public static string GetEnumComment(this Enum enumeration)

    {

        return GetEnumComment(enumeration, false);

    }

 

    private static string GetEnumComment(Enum enumeration, bool nullIfNotExists)

    {

        var fi = enumeration.GetType().GetField(enumeration.ToString());

 

        if (fi == null)

        {

            return nullIfNotExists ? null : string.Empty;

        }

 

        var attr = (EnumCommentAttribute[])(fi.GetCustomAttributes(typeof (EnumCommentAttribute), false));

 

        if (attr.Length > 0)

        {

            return attr[0].ResourceComment;

        }

 

        var result = GetEnumDescription(enumeration);

        return nullIfNotExists ? null : result;

    }

 

    public static bool HasValue(this Enum enumeration)

    {

        if (enumeration == null)

        {

            return false;

        }

 

        var type = enumeration.GetType();

        // ReSharper disable once CheckForReferenceEqualityInstead.1

        if (type.Equals(null))

        {

            return false;

        }

 

        var fi = enumeration.GetType().GetField(enumeration.ToString());

        if (fi.Equals(null))

        {

            return false;

        }

 

        return true;

    }

}

 

Contract:

public class EnumEntry

{

    public int Id { get; set; }

    public string Name { get; set; }

    public string DisplayName { get; set; }

    public string Comment { get; set; }

}

Tests:

internal class EnumerationExtensionsSpecs{

    internal class EnumBase

    {

        protected enum DummyEnum

        {

            [Description("Desc1")]

            WithDescription = 1,

 

            WithoutDescription = 2

        }

 

        protected enum DummyEnum2

        {

            [EnumComment("Comment1")]

            WithComment = 1,

 

            WithoutComment = 2

        }

 

        protected enum DummyEnum3

        {

            WithValue = 1,

 

            WithoutValue

        }

 

        protected enum DummyEnum4

        {

            [Description("DisplayName1")]

            [EnumComment("Comment1")]

            Value1 = 1,

 

            [Description("DisplayName2")]

            [EnumComment("Comment2")]

            Value2 = 2

        }

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class Wenn_eine_Enum_2_Werte_besitzt_und_diese_aufgelistet_werden_sollen : EnumBase

    {

        private static IList<EnumEntry> _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = (typeof (DummyEnum4)).GetEnumerationEntries(); };

 

        private It dann_ergeben_sich_daraus_2_Listenwerte = () => _result.Count.ShouldEqual(2);

 

        private It dann_werden_alle_Daten_von_Wert1_auf_den_Listeneintrag1_gemappt = () => (

                                                                                               _result.First().Id == 1

                                                                                               && _result.First().Name == "Value1"

                                                                                               && _result.First().DisplayName == "DisplayName1"

                                                                                               && _result.First().Comment == "Comment1"

                                                                                           )

                                                                                               .ShouldBeTrue();

 

        private It dann_werden_alle_Daten_von_Wert2_auf_den_Listeneintrag2_gemappt = () => (

                                                                                               _result.Last().Id == 2

                                                                                               && _result.Last().Name == "Value2"

                                                                                               && _result.Last().DisplayName == "DisplayName2"

                                                                                               && _result.Last().Comment == "Comment2"

                                                                                           )

                                                                                               .ShouldBeTrue();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_description_and_null_if_emtpy_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithDescription.GetEnumDescriptionIfExists(); };

 

        private It should_resolve_the_description_text = () => _result.ShouldEqual("Desc1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_description_and_null_if_emtpy_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithoutDescription.GetEnumDescriptionIfExists(); };

 

        private It should_resolve_null = () => _result.ShouldBeNull();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_description_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithDescription.GetEnumDescription(); };

 

        private It should_resolve_the_description_text = () => _result.ShouldEqual("Desc1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_description_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithoutDescription.GetEnumDescription(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldEqual("WithoutDescription");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_comment_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithComment.GetEnumComment(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldEqual("Comment1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_comment_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithoutComment.GetEnumComment(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldEqual("WithoutComment");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_comment_and_null_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithComment.GetEnumCommentIfExists(); };

 

        private It should_resolve_the_description_text = () => _result.ShouldEqual("Comment1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_description_and_null_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithoutComment.GetEnumCommentIfExists(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldBeNull();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_has_a_value : EnumBase

    {

        private static bool _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum3.WithValue.HasValue(); };

 

        private It should_return_true = () => _result.ShouldBeTrue();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_has_no_value : EnumBase

    {

        private static bool _result;

 

        private Establish context = () => { };

 

        private Because of = () =>

                             {

                                 DummyEnum3? d3 = null;

                                 _result = d3.HasValue();

                             };

 

        private It should_return_false = () => _result.ShouldBeFalse();

    }

 

    internal class When_Enum_is_not_decorated_with_FlagsAttribute

    {

        private static TestEnum_MissingFlagAttribute MyEnum;

 

        private Because of = () => { MyEnum = TestEnum_MissingFlagAttribute.Val2 | TestEnum_MissingFlagAttribute.Val3; };

 

        private It should_return_False1 = () => MyEnum.ByteIsSet(TestEnum_MissingFlagAttribute.Val1).ShouldBeFalse();

 

        private It should_return_False2 = () => MyEnum.ByteIsSet(TestEnum_MissingFlagAttribute.Val2).ShouldBeFalse();

 

        private It should_return_False3 = () => MyEnum.ByteIsSet(TestEnum_MissingFlagAttribute.Val3).ShouldBeFalse();

 

        private enum TestEnum_MissingFlagAttribute

        {

            Val1,

            Val2,

            Val3

        }

    }

 

    internal class When_Enum_is_compared_with_wrong_Enum_type

    {

        private static TestEnum1 MyEnum1;

 

        private Because of = () => { MyEnum1 = TestEnum1.Val2 | TestEnum1.Val3; };

 

        private It should_return_False1 = () => MyEnum1.ByteIsSet(TestEnum2.Val1).ShouldBeFalse();

 

        private It should_return_False2 = () => MyEnum1.ByteIsSet(TestEnum2.Val2).ShouldBeFalse();

 

        private It should_return_False3 = () => MyEnum1.ByteIsSet(TestEnum2.Val3).ShouldBeFalse();

 

        [Flags]

        private enum TestEnum1

        {

            Val1 = 0x01,

            Val2 = 0x02,

            Val3 = 0x04

        }

 

        [Flags]

        private enum TestEnum2

        {

            Val1 = 0x01,

            Val2 = 0x02,

            Val3 = 0x04

        }

    }

 

    internal class When_Enum_contains_enum_value

    {

        private static TestEnum MyEnum;

 

        private Because of = () => { MyEnum = TestEnum.Val2 | TestEnum.Val3; };

 

        private It should_return_False_for_val1 = () => MyEnum.ByteIsSet(TestEnum.Val1).ShouldBeFalse();

 

        private It should_return_True_for_val2 = () => MyEnum.ByteIsSet(TestEnum.Val2).ShouldBeTrue();

 

        private It should_return_True_for_val3 = () => MyEnum.ByteIsSet(TestEnum.Val3).ShouldBeTrue();

 

        [Flags]

        private enum TestEnum

        {

            Val1 = 0x01,

            Val2 = 0x02,

            Val3 = 0x04

        }

    }

}

Precondition Methoden sind Gift für die API

Ich sehe öfters eine API, bei der ich vor dem eigentlichen Aufruf der gewünschten Methode bzw. nach dem Erzeugen des Objekts zuerst eine Initialize oder Configure Methode aufrufen muss.

   1: var foo = new Foo();

   2: foo.Initialize(Bar bar);

   3: foo.Execute();

Das finde ich persönlich unschön, schließlich muss der Aufrufer somit die Komponente gut kennen. Besser wäre, wenn nach der Erzeugung nur die Initialize Methode zur Verfügung stünde. Erst nach dem Aufruf selbiger sollte es möglich sein die Execute Methode aufzurufen.

Hier zwei Beispiele, wie sich das technisch lösen lässt:

   1: public class Foo

   2: {

   3:     private Foo() {}

   4:  

   5:     public static Foo Initialize(Bar bar)

   6:     {

   7:         //do your stuff

   8:         return new Foo();

   9:     }

  10:  

  11:     public void Execute()

  12:     {

  13:         //do something

  14:     }

  15: }

Auf Foo wird dann wie folgt zugegriffen (beachte: this ist in diesem Fall eine Instanz von Bar):

   1: var foo = Foo.Initialize(this);

   2: foo.Execute();

 

Hier eine Lösung für die Verwendung in einem IoC Container. Statt Foo und Bar mit kleinen Änderungen.

Interface1:

   1: public interface ISetupParallelization

   2: {

   3:     IRunParallelization SetupWith(ParallelizationConfig config);

   4: }

Interface2:

   1: public interface IRunParallelization

   2: {

   3:     ParallelizationStatus Run(Queue<dynamic> jobs);       

   4: }

Implementierung beider Interfaces in einer Klasse:

   1: public class CalculationParallelizer : ISetupParallelization, IRunParallelization

   2: {

   3:     public CalculationParallelizer()

   4:     {

   5:     }

   6:  

   7:     public ParallelizationStatus Run(Queue<dynamic> jobs)

   8:     {

   9:         //do something        

  10:     }

  11:  

  12:     public IRunParallelization SetupWith(ParallelizationConfig config)

  13:     {

  14:         _config = config;

  15:         return this;

  16:     }    

  17: }

 

Im vorliegenden Fall würde im Container nur der Service ISetupParallelization registriert werden. Wenn dir der Beitrag gefallen hat, dann vote dafür und/oder hinterlasse einen Kommentar. Hier geht es zu Videos zum Thema von IoC.

SmartInspect – Vorerst keine neuen Features

Ich nutze seit mehreren Jahren SmartInspect von Gurock Software. Das Tool für Logging und Tracing hat bei mir immer gute Dienste verrichtet, sodass ich es häufig empfohlen habe. Inzwischen bin ich aber vorsichtig geworden. Ein Blick in die Programmhistorie verrät, dass in den letzten 2 1/4 Jahren lediglich folgende Änderungen durchgeführt (bezogen auf Version 3.3.4, welche am 29.11.2012 veröffentlicht wurde):

  • Added: Support for Delphi XE5 (32 + 64-bit)
  • Added: Support for Visual Studio 2013
  • Added: Support for Delphi XE4 (32 + 64-bit)
  • Added: Support for Delphi XE3 (32 + 64-bit)
  • Added: Support for Visual Studio 2012
  • Fixed: A few issues with the Delphi library that could cause an Access Violation exception (64-bit only)

Zuvor wurde mit Version 3.3.0 am 15.04.2010 das letzte Mal ein etwas umfassenderes Update veröffentlicht.

Unterm Strich lässt sich also sagen, dass in 4 Jahren wenig bis gar keine neuen Features oder Verbesserungen Einzug gehalten haben. Diejenigen, die aber SmartInspect weiterhin mit ihren neuen IDEs nutzen wollten, mussten dementsprechend neue Lizenzen erwerben. Persönlich halte ich das für höchst fragwürdig gegenüber den Kunden.

Über die Jahre hinweg reichte ich immer wieder Vorschläge zu neuen Programmfunktionalitäten ein:

  • Einen ready-to-use E-Mail Logger
  • Das Loggen von verschachtelten Objekten (LogObject loggt nicht die Inhalte von inneren Objekten)
  • Ein Tree Control
  • Ein read-to-use PostSharp Aspect (für Version 3.1)
  • Der Speicherverbrauch bei lokalem TCP Logging, sodass die geöffnete SmartInspect Instanz durchaus mal 1GB Ram verbrauchen kann
  • Ein NuGet Package

Die Antwort war stets die gleiche: Wir nehmen es in die interne Feature Request List auf, sind aber mit der Funktionalität zufrieden und planen aktuell keinen neuen Feature Release. Ein NuGet Package habe ich daraufhin selbst zusammengestellt und veröffentlicht. Gurock Software habe ich darüber informiert und ihnen angeboten, das Paket zu übertragen. Allerdings wurde ich gebeten selbiges wieder einzustellen.

In der letzten E-Mail von Januar 2014 wurde mir Folgendes mitgeteilt:

Es ist in der Tat so, dass wir uns momentan leider nicht auf neue Features für SmartInspect konzentrieren. Dies hat verschiedene Gründe, unter anderem auch den, dass wir mit dem Featureumfang von SmartInspect so insgesamt zufrieden sind. Es gibt verschiedene Dinge, die wir in Zukunft noch gerne in SmartInspect einbauen möchten, allerdings ist SmartInspect in der derzeitigen Version bereits für viele Teams nützlich. Wir veröffentlichen regelmäßig kleinere Updates für neue IDE Versionen, allerdings ist ein größeres Feature Update momentan leider nicht in Entwicklung.

Aktuell kann ich deshalb niemanden empfehlen eine Lizenz zu erwerben, solange unklar ist, wie es um die Weiterentwicklung steht. Ich kann nur die Anwender dazu aufrufen an das Unternehmen zu appellieren und es zu bitten, das Produkt wieder voran zu treiben, v.a. da es aus meiner Ansicht viel Potential hat.

Weiteres Feedback, Feature Requests oder Bugfixing Wünsche könnt ihr einfach in die Kommentare schreiben. Gerne nehme ich auch eine Statement von Gurock Software selbst mit auf.

PostSharp Fast Track Webinar – Feedback

German:

Das Webinar ging knapp 35 Minuten, obwohl in der Einladung 1 Stunde stand. Aber persönlich bevorzuge ich es, wenn Termine nicht länger gehen, sondern höchstens früher aufhören (Stichwort timeboxed). Der Sprecher hatte eine angenehme Sprechweise und war gut verständlich.

Leider waren die ersten 10 Minuten reine Basics, z.B. wurde der Unterschied zw. PostSharp und Containern erläutert. Das pfeifen die Vögel ja inzwischen von allen Dächern, von daher: Lass weg, hau mehr technische Infos rein. Interessant sind die neuen Möglichkeiten mit den ready-to-use Aspekten, die die Ultimate Lizenz mitbringt. Primär ist interessant wie gut das seit Version 3 integriert ist. Typischerweise kamen die Einstiegsbeispiele der vergangenen Jahre wieder zur Geltung: INotifiyPropertyChanged, Logging, Transaction. Auch das zähle ich noch zu den Basics.

Dann ging es weiter mit Möglichkeiten im Kontext von Threading-Szenarien. Die meisten Schwierigkeiten werden out-of-the-box abgedeckt. Richtig interessant wurde es für mich persönlich dann mit den neuen Architectual Constraints, die zumindest in dem Beispiel einen starken Eindruck hinterlassen haben. CQRS enforced by PostSharp – geil! Die Multicast-Option für projektweite Einstellungen waren mir zwar bekannt, allerdings hatte ich mich dazu noch nicht eingelesen, wie ich zu meiner Schande gestehen muss. Von daher war ich froh, dass dies ebenfalls gezeigt wurde.

Generell hat Adam in den 35 Minuten wirklich sehr viel gezeigt. Für mich wäre es noch spannender gewesen, wenn er sich stark auf die Neuerungen von PostSharp 3 und v.a. 3.1 konzentriert hätte. Sprich: Ein technischer Deep Dive. So wie die Folien aufgebaut waren, wie er das Webinar führte und basierend auf seiner Biographie, denke ich, dass ihm das problemlos möglich wäre. Deshalb an dieser Stelle die Bitte: Deep Dive für Fortgeschrittene!

 

English:

The webinar only took about 35 minutes, although it said it would last an entire hour in the invitation. But it’s an personal preference of mine, if meetings don’t last too long, but end prematurely (keyword: timeboxed). The speaker had an excellent manner of speaking and was very comprehensible.

Unfortunately the first 10 minutes covered only the basics, for example the difference between Postsharp and Containers was explained. That’s considered common knowledge nowadays, so: Leave that out, focus on technical info! The new possibilities of the ready-to-use aspects, that the Ultimate Licencse brings along, are more interesting. The good integration in version 3 is of particular interest. The usual beginner examples of last year made another occurance: INotifiyPropertyChanged, Logging, Transaction. I consider those common knowledge as well.

Next, he showed some threading practices. There are some handy features which cover most of the common difficulties in multithreading. After that, he started with my personal favorite: Architectual Constraints. Very impressive. CQRS enforced by PostSharp – awesome. I knew about Multicasting aspects, but to my shame I have to admit that I haven’t already concerned myself with that. So thanks for showing it.

Summing up: Adam did a great job, showing lots of things in just about 35 minutes. Personally, I would have been more excited about just the features of PostSharp 3 and especially 3.1 (like these). Listening to him, seeing his demo and reading his biography excerpt , I’m convinced he could come up with a technical deep dive without any difficulties. So my request to PostSharp: Deep dive for “advanced learners”.

NET Framework 4.5 für Build Server

Wer seine Software auf .NET 4.5 migriert, sollte darauf achten, dass er sich nicht nur das “normale” Setup von Microsoft herunterlädt, denn sonst kann es auf dem Build Server zu folgendem Fehler kommen (dieser tritt nicht bei einem installierten Visual Studio 2012 auf, was auf einem Build Server aber nichts verloren hat):

 

c:\Windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(983,5): warning MSB3644: The reference assemblies for framework „.NETFramework,Version=v4.5“ were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend.

 

Der Pfad an sich ist nicht falsch, da dieser sich mit der neuen Version nicht ändert. Es handelt sich um ein sogenanntes in-place Update, bei dem vorhandenen Verzeichnisse und Registrierungsschlüssel wiederverwendet werden. Damit werden alle 4.0 Assemblies entfernt. Eine Deinstallation von 4.5 bewirkt somit, dass man ohne .NET Framework endet.

 

Der folgende Screenshot zeigt den Stand nachdem eine bestehendes NET Framework 4 mit 4.5 aktualisiert wurde.

image

 

Ein funktionierender Build Server zeigt sich aber so:

image

 

Dazu muss das Windows SDK for Windows 8 (nicht 8.1!) installiert werden, welches ihr hier findet. Mit der Anwendung könnt ihr das eigentliche Setup herunterladen:

image

 

Nach der Installation am besten nochmal die Windows Updates laufen lassen und danach den grünen Build genießen.

NuGet Packages auf neue NET Version aktualisieren

Aktuell migrieren wir unsere Projekte / Solutions von .NET Framework 4.0 auf 4.5. Die Herausforderung bestand darin alle NuGet Packages, von denen wir viele verwenden, umzustellen. Ein Blick in die packages.config verrät, dass die installierten Pakete immer gegen eine entsprechende .NET Version gebunden sind:

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <packages>

   3:   <package id="EntityFramework" version="5.0.0" targetFramework="net40" />

   4: </packages>

 

Dies lässt sich auch in den zugehörigen Assemblies ablesen:

image

Unter Path wird in diesem .NET 4.0 Projekt beispielsweise auf “packages\EntityFramework.5.0.0\lib\net40” verwiesen. Darüber ist es beim EntityFramework Package so, dass die Installation der aktuellen 5er Version in obigem Projekt lediglich die 4.4er Version referenziert wird. In der packages.config steht allerdings weiterhin 5.0.0.

Nach der Änderung des Target Frameworks auf 4.5, ergibt sich folgende Zeile in der packages.config:

   1: <package id="EntityFramework" version="5.0.0" targetFramework="net40" requireReinstallation="True" />

 

Der requiresReinstallations-Schalter bewirkt Warnungen beim Build. Um nun nicht alle packages von Hand aktualisieren zu müssen, gibt es den Befehl

update-package -reinstall

welcher die komplette Solution samt aller Projekte durchgeht und alle Packages deinstalliert und danach wieder gemäß dem gesetzten Target Framework installiert.

Lazy Loading mit IoC

Update vom 04.09.2013: Beachtet bitte die Kommentare. Das Lazy-Feature kann auch erhebliche Performance-Einbußen zur Folge haben. Es kommt darauf, was der Konstruktur des Lazy-Objekts noch an Abhängigkeiten hat und welche Logik in selbigem noch ausgeführt wird.

In diesem Webcast zeige ich welche Möglichkeiten Castle.Windsor für das Lazy Loading von Abhängigkeiten bietet. Das Feature wurde in Version 3 eingeführt. Weiterführenden Inhalt welche Performance Gedanken dahinter stecken und wann idealerweise der Graph des Container geladen werden sollte, findet ihr hier und hier.

Für diejenigen, die einen anderen Container nutzen: Falls euer Framework die Funktionalität nicht out-of-the-box mitliefert, so stellt sich eine eigene Lösung nicht als allzu schwierig heraus. In dem oben zuletzt erwähnten Link gibt es dazu ein Code Sample.

Als grobe Richtlinie kann man sich merken:

Das Laden von weiteren Abhängigkeiten wird v.a. dann “teuer”, wenn die Implementierungen in Assemblies liegen, die noch nicht geladen wurden.

MindManager mit SharePoint verheiraten

Kürzlich haben wir für die IT das Tool MindManager von Mindjet lizenziert. Mehr zur Software und die Entscheidung diesbezüglich werde ich in einem späteren Blogeintrag thematisieren.

In dem vorliegenden Video zeige ich euch, wie ihr einige Unschönheiten im Zusammenspiel von MindManager und SharePoint Foundation 2010 in den Griff bekommt. Unter anderem wird ein entsprechendes Icon für mmap-Dateien eingebunden und das Öffnen der Dateien ermöglicht. Ist beispielsweise der Dateityp den Internet Information Services (IIS) nicht bekannt, kann die Map nur als ZIP-Datei heruntergeladen werden.

Weiterführende Links:

TDD as if you meant it

Als Ralf Westphal im Juli in der .NET UserGroup Rhein Neckar eine Session zu TDD as if you meant it hielt, wollten meine Karlsruhe Kollegen und ich uns das nicht entgehen lassen. Neue Denkansätze zu TDD als state-of-the-art Praktik sind für mich persönlich aktuell ein Dauerthema, da ich immer noch Potential nach oben sehe.

Als Vorbereitung las ich die zugehörige Artikelserie aus der dotnet pro. Ich will jetzt nicht das dort Geschriebene kommentieren oder gar hier ausführen, denn als .NET Developer haben hoffentlich die meisten Zugriff auf den Inhalt. Stattdessen hier kurz die Punkte, die für mich nochmal klarer wurden:

 

  • Für TDDaiymi wird eine feststehende API benötigt, d.h. im Vorhinein muss der Contract stehen
  • TDDaiymi hilft dann dabei die Aspekte innerhalb der Klasse zu identifizieren, sprich die innere Struktur wird entwickelt
  • TDDaiymi ist (zeit-)aufwendiger als “klassisches” TDD und bietet sich v.a. bei komplexen, ggf. nicht gänzlich durchdrungenen Problemen an
  • Als Praktik ist es sicherlich für Coding Dojos interessant, da man für saubere Aspekttrennung und implizite Annahmen (die nicht implizit sein sollten) sensibilisiert wird

 

Das Video habe ich nachbearbeitet, um die Ton- und Bildqualität zu steigern, allerdings ist es zugegebenermaßen nicht optimal. Danke an Ralf nochmal für den Vortrag und die Freigabe des Videos. Den Code gibt es ähnlich in der dotnet pro. Wenn jemand schon Erfahrungen mit der Praktik gesammelt hat, dann würde ich mich über Feedback freuen.

Castle Windsor Dependencies Tracking

Deployments für Anwendungen zu erstellen, welche modular über einen IoC Container wie Castle.Windsor aufgebaut sind, ist nicht ganz simpel. Zumindest ging es mir so, als ich vor der Herausforderung stand eine Konsolenanwendung zu deployen, welche im Einstiegspunkt eine Komponente auflöst und darauf eine Methode aufruft, die den gesamten Bearbeitungsprozess startet. Alle weiteren Abhängigkeiten werden dann über den internen Graphen des Containers aufgelöst. Findet er eine Komponente nicht, kommt es zur Exception:

image

Ich halte es in meinen Projekten so, dass die Kontrakte immer in einer separaten Assembly liegen. Als Namenskonvention erhält die DLL ein “.Contracts”. Ein Beispiel:

  • UAR.Demo //Implementierung
  • UAR.Demo.Contracts //Kontrakte

So weit wie möglich sind dann in der Implementierung alle Klassen internal oder private. Über einen öffentlichen Installer wird beim Start der Anwendung die Registrierung der angebotenen Kontrakte samt derer Implementierungen im Container vorgenommen.

Da demzufolge keinerlei Referenzen mehr zwischen den Implementierungen bestehen und ich in der oben erwähnten Konsolenanwendung nun nur genau einen Einstiegspunkt habe, der sich die initiale Komponente auflöst und startet, fehlen weitere benötigte Assemblies.

Als ich über Twitter das Castle Team diesbezüglich angesprochen habe, kam folgende Antwort:

it might not be trivial. You can walk the graph, but better option would be to have clean layering so it’s obvious

Leider half mir die Aussage nicht weiter, da ich keinen „clean layering”-Ansatz sehe, der besagtes Problem auflöst. Vielleicht kann mir einer der Leser weiterhelfen?

Als Lösung habe ich mich in das Event der Komponentenerstellung von Castle.Windsor gehängt. Wie dies funktioniert, seht ihr in dem Webcast. An dieser Stelle will ich noch den Code veröffentlichen und um Feedback bitten.

 

 

Extension Methods für IWindsorContainer:

   1: static class CastleWindsorExtensions

   2:     {

   3:         public static IWindsorContainer EnableDependencyTracking(this IWindsorContainer container)

   4:         {

   5:             var containerInfo = new ContainerDependencyTracker();

   6:  

   7:             container

   8:                 .Register(Component.For<ITrackContainerDependencies>()

   9:                 .Instance(containerInfo)

  10:                 .LifestyleSingleton());

  11:  

  12:             container.Kernel.ComponentCreated += containerInfo.AddRequiredAssembly;

  13:  

  14:             return container;

  15:         }

  16:  

  17:         public static IWindsorContainer EnableMemoryDiagnostic(this IWindsorContainer container)

  18:         {

  19:             //have a look at http://docs.castleproject.org/Windsor.Performance-Counters.ashx?HL=lifecycledcomponentsreleasepolicy

  20:             var diagnostic = LifecycledComponentsReleasePolicy

  21:                                 .GetTrackedComponentsDiagnostic(container.Kernel);

  22:             var counter = LifecycledComponentsReleasePolicy

  23:                                 .GetTrackedComponentsPerformanceCounter(new PerformanceMetricsFactory());

  24:             container.Kernel.ReleasePolicy = new LifecycledComponentsReleasePolicy(diagnostic, counter);

  25:  

  26:             return container;

  27:         }

  28:     }

 

Interface für die Tracking Implementierung:

   1: public interface ITrackContainerDependencies

   2: {

   3:     IEnumerable<Assembly> RequiredAssemblies { get; }

   4:  

   5:     int RegisteredComponents { get; set; }

   6:  

   7:     void AddRequiredAssembly(ComponentModel model, object instance);

   8:  

   9:     StringBuilder ResolvedComponentsInARow { get;}

  10: }

 

Implementierung für das Tracking:

   1: class ContainerDependencyTracker : ITrackContainerDependencies

   2: {

   3:     public ContainerDependencyTracker()

   4:     {

   5:         ResolvedComponentsInARow = new StringBuilder();

   6:     }

   7:  

   8:     readonly List<Assembly> _requiredAssemblies = new List<Assembly>();

   9:  

  10:     int Number;

  11:     public StringBuilder ResolvedComponentsInARow

  12:     {

  13:         get;

  14:         private set;

  15:     }

  16:  

  17:     public IEnumerable<Assembly> RequiredAssemblies

  18:     {

  19:         get

  20:         {

  21:             return _requiredAssemblies.Distinct().ToList();

  22:         }

  23:     }

  24:  

  25:     public int RegisteredComponents

  26:     {

  27:         get;

  28:         set;

  29:     }

  30:  

  31:     public void AddRequiredAssembly(ComponentModel model, object instance)

  32:     {

  33:         Number += 1;           

  34:         ResolvedComponentsInARow.AppendLine(string.Format("{0}: {1}", Number, model.Implementation));

  35:  

  36:  

  37:         foreach (var service in model.Services)

  38:             _requiredAssemblies.Add(service.Assembly);

  39:  

  40:         _requiredAssemblies.Add(model.Implementation.Assembly);

  41:     }

  42: }

 

In dem Video gehe ich auch kurz auf die Auffindung von Speicherlecks ein. Die Implementierung ist oben in den Extensions enthalten. Es sei an dieser Stelle auf folgende Seite im Castle Wiki verwiesen.

Entity Framework Webcasts – Testing

Im siebten Teil meiner Entity Framework Webcast Serie widme ich mich dem Thema Testing. Zunächst nehmen wir uns die fertigen Queries vor, welche sich auch gut ohne Datenbank testen lassen. Schließlich will nicht jeder zum Testen einer Abfrage immer eine entsprechende Datenbank hochfahren und die Daten dafür erzeugen.

Danach stellen wir in einem Mini-Business-Pseudo-Layer ein Szenario nach, welches uns dedizierte Daten für die weitere Verarbeitung liefert, sodass das Testen der Geschäftslogik ebenfalls ohne Datenbank erfolgen kann.

Als BDD Framework für die Unit Tests verwende ich Machine.Specifications und als Mocking Framework kommt FakeItEasy zum Einsatz.

Hier noch die zwei Code Beispiele:

 

Beispiel 1: Testen der Query

   1: [Subject(typeof(GetAddressByCity))]

   2: public class When_addresses_contains_exactly_one_matching_entry

   3: {

   4:     static GetAddressByCity Sut;

   5:     static List<Address> Addresses;

   6:     static Address Actual;

   7:     static Address TestCity;

   8:  

   9:     Establish context = () =>

  10:     {

  11:         TestCity = new Address {City = "test city"};

  12:         Addresses = new List<Address>

  13:         {

  14:             new Address {City = "Karlsruhe"},

  15:             TestCity

  16:         };

  17:         Sut = new GetAddressByCity("test city");

  18:     };

  19:  

  20:     Because of = () =>

  21:     {

  22:         Actual = Sut.Execute(Addresses.AsQueryable());

  23:     };

  24:  

  25:     It should_return_exactly_this_address = () => 

  26:             Actual.ShouldEqual(TestCity);

  27: }

 

Beispiel 2: Testen von Geschäftslogik

   1: class EmployeeBusinessLogicSpecs

   2: {

   3:     [Subject(typeof(EmployeeBusinessLogic))]

   4:     class When_hire_date_is_unknown

   5:     {

   6:         static IUnitOfWork Uow;

   7:         static EmployeeBusinessLogic Sut;

   8:         static Employee DummyEmployee;

   9:  

  10:         Establish context = () =>

  11:         {

  12:             DummyEmployee = new Employee {EmployeeID = 1, 

  13:                     FirstName = "Uli", LastName = "Armbruster"};

  14:  

  15:             Uow = A.Fake<IUnitOfWork>();

  16:  

  17:             A

  18:                 .CallTo(() => Uow.ExecuteQuery(

  19:                     A<GetEmployeeById>

  20:                     .That

  21:                     .Matches(q => q.EmployeeId == DummyEmployee.EmployeeID)

  22:                                   ))

  23:                 .Returns(DummyEmployee);

  24:  

  25:             Sut = new EmployeeBusinessLogic(Uow);

  26:         };

  27:  

  28:         Because of = () => Sut.EnsureValidHireDate(DummyEmployee.EmployeeID);

  29:  

  30:         It should_update_it = () => DummyEmployee.HireDate.ShouldNotBeNull();

  31:  

  32:         It should_save_the_changed_hire_date = () => A

  33:             .CallTo(() => Uow.Commit())

  34:             .MustHaveHappened(Repeated.Exactly.Once);

  35:     }

  36: }

 

 

Weitere Quellen:

FakeItEasy und Castle.Core

Es ist vollbracht. Das Team um FakeItEasy hat endlich die Abhängigkeit zu Castle.Core entfernt, indem es selbige mit IL Merge in die eigene DLL gepackt hat. Damit lassen sich Castle und FakeItEasy NuGet Packages separat updaten.

Vorher: image

PM> Install-Package FakeItEasy -Version 1.7.4626.65
Attempting to resolve dependency ‚Castle.Core (≥ 3.1.0.0)‘.
Successfully installed ‚Castle.Core 3.1.0‘.
Successfully installed ‚FakeItEasy 1.7.4626.65‘.
Successfully added ‚Castle.Core 3.1.0‘ to FakeAndCastle.
Successfully added ‚FakeItEasy 1.7.4626.65‘ to FakeAndCastle.

 

Nachher:

image   PM> install-package FakeItEasy
   Successfully installed ‚FakeItEasy 1.10.0‘.
   Successfully added ‚FakeItEasy 1.10.0‘ to FakeAndCastle.

 

 

 

 

Zurückzuführen ist das auf ein großes Interesse seitens der Community. Auf meine Bitte hin wurde in die aktuelle Version 1.10.0 Castle.Core 3.2.0 integriert.

 

Good job, guys!

Entity Framework Webcasts – Stored Procedures und SQL Queries

Im sechsten Teil meiner Entity Framework Webcast Serie zeige ich, wie die IUnitOfWork erweitert werden muss, um SQL Abfragen und Befehle auszuführen. Im Fokus stehen Value Functions und Stored Procedures.

 

 

Dafür stellt das Entity Framework 2 Schnittstellen bereit:

Ein wichtiger Hinweis an dieser Stelle, welcher eingehender in meinem Webcast erwähnt wird: Zum Ausführen einer SQL Query existieren 2 Implementierungen:

Der wesentliche Unterschied besteht darin, dass im ersten Fall die zurückgegebenen Entitäten nicht getrackt werden, im zweiten hingegen schon. Das ist leicht nachvollziehbar, so muss bei DbContext.Database.SqlQuery ein generischer Parameter, der den Rückgabewert darstellt, übergeben werden. Im Falle von DbSet.SqlQuery ist dies nicht nötig, schließlich befindet man sich zum Zeitpunkt der Abfrage auf einem konkreten Set an Entitäten, die über den Context aufgelöst wurden.

Meine vorgestellte Lösung geht den gleichen Weg wie die in Teil 3 beschriebenen Queries, d.h. es werden quasi SQL Query und Command Repositories angelegt, um einer Streuung innerhalb der Anwendungslandschaft entgegenzuwirken.

Schließlich noch zwei Sicherheitshinweise:

  • Die SQL Befehle werden mit den Berechtigungen des Contexts bzw. des darunterliegenden Sicherheitstoken ausgeführt
  • Beim Erstellen des SQL Strings sollte aus Sicherheitsgründen gegenüber Injections mit String.Format() oder alternativ mit SqlParameters gearbeitet werden.

 

I wrote this blog post as an answer of a question from the Netherlands, so I want to complete this article with a quote:

“When you execute a SqlQuery from Database, the results are never tracked by the context, even if the query returns types that are in the model an known by the context. If you do not want the results to be changed-tracked, use DbContext.Databae.SqlQuery.

Results of a DbSet.SqlQuery will be tracked by the context. Ensuring that results are change-tracked is the primary reason you would choose to use DbSet.SqlQuery over Database.SqlQuery.” (Programming Entity Framework, Page 226)

 

Weitere Quellen:

Entity Framework Webcasts Serie

In den vergangenen Tagen gingen Teil 4 und 5 meiner Entity Framework Webcast Serie online. Außerdem ist geplant in den kommenden Tagen noch Teil 6 zu veröffentlichen. Das liegt darin begründet, dass ich verstärkt per E-Mail adressiert mit der Bitte wurde, die restlichen Teile nachzuschieben. Außerdem erhielt ich mehrfach Anfragen bzgl. diverser Schwerpunkte, die ich in zukünftigen Beiträgen aufgreifen möge. Den Bitten werde ich so weit mir möglich nachkommen.

Deshalb an dieser Stelle nochmal der Hinweis, dass man mich über die üblichen Kommunikationskanäle gerne ansprechen darf. Das ein oder andere Feedback hilft mir auch dabei mich immer wieder aufzurappeln und in der Freizeit derartigen Inhalt zu produzieren.

 

Hier ein kurzer Auszug aus einer Mail, die ich gerade erst vergangene Woche erhalten habe:

Hallo Herr Armbruster,

erstmal kompliment für die 3 m.E. sehr guten Videos. Gerne hätte ich noch mehr bzgl. Entity Framework gesehen. Die Art und Weise wie die Thematik erläutert wird, ist Ihnen sehr gut gelungen.

Schön wäre z.B. noch:

– Genaue Erläuterung der Konfigurationsdatei

– WCF in Verbindung mit EF

– Architekturgestaltung bis hin zur UI

Die Gestaltung und Auslagerung der Queries im Teil3 ist für mich ein riesen Lernfortschritt gewesen. Probleme habe ich jetzt bekommen, als ich die Abfrage, also eine IQuery über einen WCF Dienst laufen lassen wollte. Dieser akzeptiert ja soweit mir bekannt ist, keine generischen Typen.

Können Sie hierzu eine kurze Info geben, wie dies realisiert werden kann?

 

 

Aktuell stehen folgende 5 Webcasts in Full HD Auflösung bereit:

  • Teil 1 – DbContext und POCOS: Wir wechselt man auf den neuen DbContext des Entity Framework 5 und erzeugt sich POCO Objekte frei von jeglichen Technologie-spezifischen Referenzen.
  • Teil 2 – UnitOfWork und IoC: Lose Kopplung ist in diesem Beitrag das Ziel. Der Client verliert vollständig seine Abhängigkeit zum Entity Framework bzw. zur Persistenzschicht.
  • Teil 3 – Queries: Hier zeige ich wie sich ein Query Repository aufbauen und Redundanz vermeiden lässt.
  • Teil 4 – Eigener Kontext: Dieses Video erläutert die Implementierung eines eigenen Kontexts.
  • Teil 5 – Weitere Datenbanken: Der nächste wichtiger Schritt ist das Einbinden weiterer Datenbanken. Der Vorzug hierbei ist die implizite Auflösung des entsprechenden Modells auf Basis von Konventionen.
  • Teil 6 – Stored Procedures und SQL Queries: In Anlehnung an Teil 3 baue ich hier ein entsprechendes Repository für reine SQL Abfragen und Stored Procedures auf.

Der vollständige Quellcode steht in dem Repository ‘MyCleanEntityFramework’ in meinem Github Account zur Verfügung.

PostSharp & SmartInspect

Dieser Blogeintrag liefert ein paar weiterführende Informationen zu meinem zugehörigen Webcast. Darin thematisiere ich wie sich die Geschäftslogik frei von Infrastructure Code halten lässt. Eine Möglichkeit dazu bietet z.B. aspektorientierte Programmierung. Im .NET Umfeld ist PostSharp sehr gut geeignet. In dem Webcast zeige ich wie wir durch den Einsatz in Verbindung mit einem starken Logging Framework wie SmartInspect Concerns wie Logging, Exception Handling, Performance Measuring und Tracing umsetzen.

Wer gerne den Code hätte, kann mich über die üblichen Kanäle wie Twitter und Xing anschreiben. Die SmartInspect Konfiguration sieht wie folgt aus:

AppName = UAR
Connections = tcp(caption="Hello World")
DefaultLevel = Message
Enabled = True
Level = Message
SessionDefaults.Level = Message
SessionDefaults.Active = True
SessionDefaults.Color = 0x00FF40

Webcast:

PostSharp und SmartInspect zur Reduzierung der Code Vermischung

Aktualisierung vom 11.11.12 um 13.50:

Weil die Frage nach Performance aufkam, noch kurz zwei Infos: Über SkipPostSharp=false in den Build Skripten kann PostSharp deaktiviert werden. Außerdem hat Daniel Marbach eine Analyse mit 125000 Codezeilen gemacht und der CompileTime Ovewrhead lag bei lediglich einer Sekunde.

Clean Code Series – Episode 1

Hier der Blogeintrag zu dem entsprechenden Video. Es geht um ein Frage eines Bekannten, der SAP Lösungen entwickelt und der von mir Vorschläge für einen Ansatz einer Problemdomäne aus seiner Praxis haben wollte.

Hier die Modalitäten:

Ausgangssituation:

· Wir haben eine Entität, beispielsweise eine Liste von Aktivitäten

· Diese Entität filtert abhängig vom angemeldeten User einige Aktivitäten und liefert den Rest an den Aufrufer zurück

· Der Filtermechanismus und die eigentliche Liste sind auf zwei Klassen aufgeteilt, die beide dasselbe Interface implementieren

· Beide Klassen hängen nur in Form einer Aufrufhierarchie voneinander ab und sind in Reihe geschaltet (Decorator)

· Der Filtermechanismus hat Abhängigkeiten zu einer Datenbankzugriffsklasse

Das schreit ja förmlich nach IoC: Zusammenbauen einer Aufrufhierarchie über Decorator, dazu ggf. weitere Abhängigkeiten auflösen.

Schön. Aber leider ist es eine schlechte Idee, konkrete Entitäten über einen IoC Container zu erzeugen.

Was also tun? Factories bauen?

Ich denke nicht, wäre es nicht geschickter, auf die Klassen das Prototype-Pattern anzuwenden?

Der IoC erzeugt dann lediglich einen Prototypen, der von den Erzeugern konkreter Arbeitslisten als Vorlage genutzt würde. Instanziierung dann folglich über prototype->clone( ).

Was denkst du?

Meine Antwort:

Für mich ist der Ansatz nicht gut modelliert. Könnt ihr das nicht in einer Klasse abbilden? Aus Sicht des Domain Driven Designs könnte man das vielleicht so implementieren:

Ihr habt eine Klasse Activities, welcher ihr beim Erzeugen die Liste alle möglichen Activities übergeben müsst. Im Konstruktor könnt ihr nach Belieben noch eine Validierung vornehmen, z.B. ob die Liste überhaupt Elemente enthält bzw. nicht null ist.

Die Gesamtmenge speichert ihr in einer privaten Variablen. Zusätzlich legt ihr ein readonly Feld für den Zugriff von außen an. Danach benötigt die Klasse nur noch Filtermöglichkeiten. In .NET würde man nur eine Methode schreiben, nämlich ApplyFilter und übergibt an diese einen Lambda Ausdruck. […] Übrigens: Wie du intern die Datenhaltung betreibst, ist dir überlassen. Ich habe es beispielsweise mit einer Liste gemacht, die wesentlich spezieller ist als Enumerable. Da könntest du sogar den Spaß in eine Textdatei wegspeichern und bei Aufruf immer wieder auslesen. Hier ist die klare Datenkapselung eingehalten. Nach außen hin sieht man eine Veröffentlichung auf Basis der sehr generischen IEnumerable Implementierung, intern verwende ich etwas sehr Spezielles.

Nach kurzer Diskussion, was ABAP hier an Möglichkeiten bietet, habe ich folgenden Vorschlag gemacht:

Clean Code Series–Episode 1

Was meint ihr?

%d Bloggern gefällt das: