Archiv der Kategorie: C#

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.

public class Installer : IWindsorInstaller
{
/// <summary>
/// Performs the installation in the <see cref="T:Castle.Windsor.IWindsorContainer" />.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="store">The configuration store.</param>
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Kernel.Resolver.AddSubResolver(new Castle.MicroKernel.Resolvers.SpecializedResolvers.CollectionResolver(container.Kernel));
container.Register(Components().ToArray());
}
private static IEnumerable<IRegistration> Components()
{
//have a look at https://github.com/castleproject/Windsor/blob/master/docs/registering-components-by-conventions.md
yield return Classes //non abstract classes; you could also use Types.* for more choices
.FromThisAssembly() //should be clear
.IncludeNonPublicTypes() //since my implementations are internal, only the interface is public
.BasedOn<IFoo>() //every class that implements this interface
.WithService.Base() //also possible: .WithServiceFromInterface()
.LifestyleTransient();
yield return Component
.For<IBar>()
.ImplementedBy<Bar>()
.LifestyleTransient();
}
}

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

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.

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”.

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.

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:

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?

Einführung in IoC Container

In diesem Webcast biete ich einen kleinen Einstieg zur Verwendung von IoC Containern. Diese Frameworks helfen bei der Umsetzung des Inversion of Control Principle unter Einsatz von Dependency Injection. Damit ist es möglich gemeinsam an an einem Feature zu arbeiten und so den Work in Progress (WiP) zu minimieren.

  • Alle Blogeinträge zu Castle.Windsor samt den im Video gezeigten Code Beispielen gibt es hier.
  • Die Projekte aus dem Webcast habe ich vorübergehend hier zur Verfügung gestellt.

 

Modularisierung durch Dependency Injection

Zusammenarbeit mit der Fachabteilung – BDD und TDD helfen

Test Driven Development (TDD) steht dafür, dass Tests vor der eigentlichen Implementierung zu schreiben sind. Es zwingt den Entwickler sich vorher genaue Gedanken über die Architektur und die Implementierung zu machen, sodass “einfaches loslaufen” und ggf. damit einhergehendes “falschlaufen” vermieden wird. Saubere Codequalität wird gefördert.

Behaviour Driven Development bringt nun einen weiteren Ansatz mit ins Spiel: Code soll ergebnisorientiert getrieben sein, d.h. die fachlichen Spezifikationen stehen im Vordergrund. Entsprechende BDD Frameworks wie Machine.Specifications (Link, gibt es auch auf NuGet) unterstützen nun dabei, dieses Ziel umzusetzen.

Damit sich auch Nicht-IT-ler und weniger Test-affine Entwickler etwas darunter vorstellen können, hier ein kleines Beispiel aus der Praxis. Wir praktizieren dies bereits seit einigen Monaten in der Form mit unseren Fachabteilungen:

Aufgabenstellung: Mehrere Produktartikel sind in einer gemeinsamen Bestellmappe zusammengefasst. Diese Mappe besitzt die Kopfdaten ‘Lieferzeit’ (LZ), ‘Bearbeitungszeit’ (BZ), ‘Bestellintervall’ (BI) und ‘nächster Mappentermin’ (MT). Der nächste Mappentermin besagt, wann die Mappe sich beim zuständigen Benutzer melden, sodass dieser eine neue Bestellung auslöst. Das Bestellintervall gibt an in welchen Abständen die Mappe neu bearbeitet werden muss, z.B. alle 3 Wochen. Die restlichen Begriffe sprechen für sich. Nun wird von der Fachabteilung gefordert, dass sogenannte ‘fiktiven Wareneingänge’ (fWE) visuell dargestellt werden (siehe Screenshot). Dabei handelt es sich um Eingänge, die rein rechnerisch in der Zukunft eingehen würden, wenn die Bestellungen so getätigt würden. Also bedarf es einer Möglichkeit, um diese Termin zu berechnen.

 

image

 

Die Tests visualisiert für die Übergabe an die Fachabteilung:

Die Tests visualisiert für die Übergabe an die Fachabteilung

Es handelt sich hier um 7 Tests. Der obere Abschnitt besagt:

Wenn von einer Bestellmappe die die Lieferzeit 5 Tage, die Bearbeitungszeit 1 Tag und das Bestellintervall 14 Tage sind und hierfür die nächsten 3 fiktiven Wareneingänge aus heutiger Sicht berechnet werden sollen:

  • Dann sollte der erste fiktive Wareneingang in 6 Tagen sein
  • Dann sollte der zweite fiktive Wareneingang in 21 Tagen sein
  • Dann sollte der dritte fiktive Wareneingang in 36 Tagen sein
  • Dann sollt es insgesamt 3 Datumsangaben errechnen

Der zweite Abschnitt ist analog zu lesen, wenn die Abkürzungen ausgeschrieben werden. Das Wesentliche daran ist, dass diese Spezifikationen von der Fachabteilungen kommen und diese so sich im Code 1 zu 1 widerspiegeln. Damit können die Entwickler die Brücke schlagen und sich mit den Anwendern in deren Domänensprache unterhalten. Die Spezifikationen garantieren, dass der Code sich wie gewünscht verhält. Die Tests lassen sich automatisiert wiederholen und werden vor jeder neuen Versionsauslieferung durchlaufen.

 

 

Zur Gänze folgen noch der Code für die Tests und die eigentliche Implementierung:

Der Code für die Tests:

   1: [Subject("Fiktive Wareneingänge")]

   2: public class Wenn_von_einer_Mappe_die_LZ_5T_die_BZ_1T_und

   3: _das_BI_14T_sind_und_3_fWE_ab_heute_berechnet_werden_sollen

   4: {

   5:     Establish context = () =>

   6:     {

   7:         Clock = new DummyClock();

   8:         Folder = new OrderProposalFolder { DeliveryPeriodInDays = 5, 

   9:                 HandlingTimeInDays = 1, OrderIntervalInDays = 14 };

  10:         Sut = new FictitiousIntakes(Clock);

  11:     };

  12:  

  13:     Because of = () =>

  14:     {

  15:         Actual = Sut.FromNow(Folder, 3);

  16:     };

  17:  

  18:     It dann_sollten_exakt_3_Datumsangaben_errechnet_werden = () => Actual.Count.ShouldEqual(3);

  19:     It dann_sollte_der_erste_fWE_in_6T_sein = () => Actual.Min().ShouldEqual(Clock.Today.AddDays(6));

  20:     It dann_sollte_der_zweite_fWE_in_21T_sein = () => Actual.ElementAt(1).ShouldEqual(Clock.Today.AddDays(21));

  21:     It dann_sollte_der_dritte_fWE_in_36T_sein = () => Actual.Max().ShouldEqual(Clock.Today.AddDays(36));

  22:  

  23:     static FictitiousIntakes Sut;

  24:     static OrderProposalFolder Folder;

  25:     static IList<DateTime> Actual;

  26:     static IClock Clock;

  27: }

  28:  

  29: [Subject("Fiktive Wareneingänge")]

  30: public class Wenn_von_einer_Mappe_die_LZ_5T_die_BZ_1T_und_das_BI_14T_sind_

  31: und_der_nächste_MT_übermorgen_ist_und_2_fWE_ab_dem_nächsten_MT_berechnet_werden_sollen

  32: {

  33:     Establish context = () =>

  34:     {

  35:         Clock = new DummyClock();

  36:         Folder = new OrderProposalFolder {NextOrderDate = Clock.Now.AddDays(2) ,DeliveryPeriodInDays = 5,

  37:                                          HandlingTimeInDays = 1, OrderIntervalInDays = 14 };

  38:         Sut = new FictitiousIntakes(Clock);

  39:     };

  40:  

  41:     Because of = () =>

  42:     {

  43:         Actual = Sut.FromNextFolderDate(Folder, 2);

  44:     };

  45:  

  46:     It dann_sollten_exakt_2_Datumsangaben_errechnet_werden = () => Actual.Count.ShouldEqual(2);

  47:     It dann_sollte_der_erste_fWE_in_8T_sein = () => Actual.Min().ShouldEqual(Clock.Today.AddDays(8));

  48:     It dann_sollte_der_dritte_fWE_in_23T_sein = () => Actual.Max().ShouldEqual(Clock.Today.AddDays(23));

  49:  

  50:     static FictitiousIntakes Sut;

  51:     static OrderProposalFolder Folder;

  52:     static IList<DateTime> Actual;

  53:     static IClock Clock;

  54: }

 

Der produktive Code, der im Programm zur Berechnung verwendet wird:

   1: public class FictitiousIntakes : ICalculateFictitiousIntakes

   2: {

   3:     readonly IClock _clock;

   4:  

   5:     public FictitiousIntakes(IClock clock)

   6:     {

   7:         _clock = clock;

   8:     }

   9:  

  10:     public IList<DateTime> FromNow(OrderProposalFolder folder, int toOrdinal)

  11:     {

  12:         return FromThis(_clock.Today, folder, toOrdinal);

  13:     }

  14:  

  15:     public IList<DateTime> FromNextFolderDate(OrderProposalFolder folder, int toOrdinal)

  16:     {

  17:         return FromThis(folder.NextOrderDate, folder, toOrdinal);

  18:  

  19:     }

  20:  

  21:     private IList<DateTime> FromThis(DateTime startDate, OrderProposalFolder folder, int toOrdinal)

  22:     {

  23:         if (toOrdinal < 1)

  24:             throw new ArgumentOutOfRangeException("toOrdinal");

  25:  

  26:         var firstIntake = startDate.AddDays(folder.DeliveryPeriodInDays + folder.HandlingTimeInDays);

  27:  

  28:         var result = new List<DateTime>();

  29:         for (int currentOrdinal = 0; currentOrdinal <= toOrdinal - 1; currentOrdinal++)

  30:             result.Add(firstIntake.AddDays(currentOrdinal * 

  31:                     (folder.OrderIntervalInDays + folder.HandlingTimeInDays)));

  32:  

  33:         return result;

  34:     }

  35: }

NServiceBus – Auf Tuchfühlung

Heute hatte ich meinen ersten Kontakt mit NServiceBus, nachdem ich mir in einigen Stunden das Konzept zu Gemüte geführt hatte. Primär dienten mir dazu die zwei Udi Dahan Videos. Da ich sehr begeistert davon war, habe ich sogleich einen Webcast erstellt. Persönlich denke ich, dass NServiceBus im .NET Umfeld durchaus in der Technologie-Evaluierung einbezogen werden sollte, wenn es um asynchrone und langlaufende Prozesse (sogenannte Sagas) geht. NServiceBus ist in gewissem Rahmen kostenfrei und steht als NuGet Package zur Verfügung. Die eigentliche Installation läuft sehr leicht von der Hand, da (wie man es selbst auch mit TopShelf bewerkstelligen kann) die erzeugte EXE für den Host bereits sämtliche Logik zur Installation enthält.

Für die Erstinstallation einfach den Parameter /installInfrastructure anhängen. Hier die Auflistung sämtlicher Parameter, die besagter Host enthält:

image

 

Besonders hervorzuheben ist die Tatsache, dass es NServiceBus absichtlich erschwert eine schlechte Architektur zu implementieren. So erhält der Client beim Verschicken eines Commands über Bus.Send lediglich als Rückgabewert ein ICallback Object, das es lediglich ermöglicht auf Fehler zu prüfen. Damit ist man gezwungen separat eine entsprechende Logikeinheit zu implementieren, die zur Verarbeitung des Events dient.

Gleichfalls wird quasi dazu eingeladen Code mit transaktionalem Verhalten in einer UnitOfWork unterzubringen und IoC zu verwenden.

Weitere nette Features:

  • Fluent API
  • Fördern des Convention over Configuration Principles
  • Trennung von Admin und Developer Verantwortungsbereichen
  • Eingebauter IoC Container (AutoFac)
  • Schnelle DB in Form von RavenDB mitgeliefert
  • Sinnvoll Benamsung wie IWantCustomInitialization

Das sind nur einige Punkte, die mir gefallen!

 

Für mich wären jetzt 2 weitere wesentliche Schritte:

  • Eigenen IoC Container einhängen, wie hier gezeigt (sehr elegant!)
  • Konfiguration der Clients im Code, um die Config beispielsweise aus der DB beim Bootstrapping zu lesen. Dies ist wohl nicht ganz trivial bzw. nicht angedacht (falls doch, bitte Info an mich!)

Webcast Strategy Pattern mit IoC

Den Webcast zu diesem Blogeintrag gibt es hier.

Strategy Pattern mit IoC sauber implementiert

Ein kurzer Hinweis vorab: Den Inhalt des Blogeintrags habe ich auch in einem Webcast zusammengefasst. Das Video gibt es auf YouTube. Den Code findet ihr hier.

In meinem letzten Blogeintrag habe ich das Strategy Pattern in der Praxis gezeigt. Primär gab es noch einen Smell, welchen ich erwähnt hatte: Das Open Closed Principle ist damit noch nicht 100%ig eingehalten und an einer Stelle heble ich mein IoC Konzept aus. Das passiert genau hier:

   1: public StateHandler(IClock clock, IBuildDefaultProposalsValues defaultValues)

   2: {

   3:     Handler = new Dictionary<SelloutStates, ICreateOrderProposal>

   4:               {

   5:                   {SelloutStates.Oversupply, null},

   6:                   {SelloutStates.Predicted, new PredictedGap(defaultValues)},

   7:                   {SelloutStates.Urgent, new UrgentGap(defaultValues, clock)},

   8:                   {SelloutStates.AppointmentWarnings, new AppointmentWarning(defaultValues, clock)},

   9:                   {SelloutStates.Unknown, null},

  10:                   {SelloutStates.Verify, new NoForecastPossible(defaultValues)},

  11:                   {SelloutStates.Ok, new UncomplicatedAppointment(defaultValues, clock)}

  12:               };

  13: }

Zum einen müsste ich die Klasse immer anpassen, wenn ich einen weiteren Status aufnehmen möchte. Zum anderen erzeuge ich mir Instanzen von Klassen, welche ich nicht über den Container erhalten habe, sprich ich hole mir die Abhängigkeiten selbst, statt mir diese gemäß Inversion of Control Principle zuweisen zu lassen. Das wäre zwar möglich gewesen, allerdings würde der Konstruktor dadurch erheblich aufblähen, weil ich pro Status eine Implementierung reinreichen müssten. Dieses Problem könnte ich über Service Location lösen, d.h. ich lasse mir lediglich den Container reinreichen und mache die Auflösung intern. Für mich persönlich auch keine schöne Lösung (Hinweis: Dafür müsste man den Container in sich selbst registrieren…).

Ob nun saubere Dependency Injection mit aufgeblähtem Konstruktor oder Service Location, in jedem Fall bliebe das Problem, dass ich jedes Mal im Container eine weitere Registrierung für einen neuen Status durchführen müsste.

Daraufhin kamen Rückfragen, wie dies am besten zu lösen sei. Das soll Thema dieses Blogeintrags und des dazu passenden Webcasts sein, v.a. weil im Netz auch nur selten saubere Implementierungen anzutreffen sind, die alle Prinzipien einhalten.

Als erstes wird der obere Code gänzlich entfernt, da dieser nicht mehr benötigt wird. Hinter dem Code steckte eigentlich das Factory Pattern. Ich lasse mir durch die Factory (abhängig vom Status) das entsprechende Objekt erzeugen und arbeite dann mit diesem weiter. Castle.Windsor als IoC Container bietet uns hierfür sogenannte interface-based factories an.

Anmerkung: Der folgende Code basiert nicht mehr auf dem bisherigen, welchen ich im vorherigen Blogeintrag verwendet habe. Im Webcast ist dazu alles erläutert und ich habe das Projekt unter Github zur Verfügung gestellt.

   1: yield return Component

   2:     .For<IchVermittleVorschlagsrechner>()

   3:     .AsFactory(c => c.SelectedWith(new VorschlagsrechnerVermittler()));

Wie man sieht, muss man lediglich das Factory-Interface, welches man zuvor selbst ausimplementiert hatte, angeben und im Anschluss ‘AsFactory’ anhängen. Als Parameter übergebe ich den Verweis auf eine Logik, welche die Factory später intern verwendet, um mir das korrekte Objekt (den korrekten Service) zurückzuliefern. Das Bedeutet, dass “new VorschlagsrechnerVermittler” der Factory, wenn auf ihr ein Aufruf stattfindet, sagen muss, was sie zurückgeben soll. Es folgt die Implementierung:

   1: public class VorschlagsrechnerVermittler : DefaultTypedFactoryComponentSelector

   2: {

   3:     protected override string GetComponentName(MethodInfo method, object[] arguments)

   4:     {

   5:         if (method.Name == "Für" && arguments.Length == 1 && arguments[0] is Bestandsstatus)

   6:         {

   7:             var status = arguments[0].ToString();

   8:             //von mir frei gewählte Konvention wie die Komponente im Container heißt

   9:             return string.Format("BerechnerFürArtikelstatus{0}", status);

  10:         }

  11:         return base.GetComponentName(method, arguments);

  12:     }

  13: }

Dieser Code macht nun genau das, was ich vorher explizit im Dictionary definiert habe, allerdings anhand einer Konvention. Die Methode in Zeile 3 liefert als String den Namen der Komponente zurück basieren auf dem Namen des Status, also z.B. ergibt sich für den Status “Ok” der Komponentenname “BerechnerFürArtikelstatusOk”.

Nun fehlt lediglich noch eine kleine Anpassung für den IoC Container. Dieser weiß bisher nichts von einer Komponente “BerechnerFürArtikelstatusOk”. Eine Möglichkeit wäre, dass ich – wie bereits eingangs erwähnt – für jeden Status die Ausprägung im Container registriere, also in diesem Fall beispielsweise so:

   1: yield return Component.For<IchErrechneArtikelZukaufsvorschlag>()

   2:     .ImplementedBy<Ok>()

   3:     .Named("BerechnerFürArtikelstatusOk")

   4:     .LifestyleTransient();

Das widerspricht immer noch dem OCP, sodass ich es wie folgt löse:

   1: yield return AllTypes.FromThisAssembly()

   2:     .BasedOn<IchErrechneArtikelZukaufsvorschlag>()

   3:     .Configure(a => a.Named(string.Format("BerechnerFürArtikelstatus{0}",

   4:         a.Implementation.UnderlyingSystemType.Name)))

   5:     .Configure(a => a.LifestyleTransient());

Zunächst hole ich mir alle Klassen aus der aktuellen Assembly, welche auf “IchErrechneArtikelZukaufsvorschlag” basieren, also alle meinen konkreten Status-Implementierungen. Im Anschluss sage ich noch, welchen Name diese Komponenten im Container bekommen sollen. Hier steht die selbe Konvention wie in dem Code Sample  zuvor. Der Vollständigkeit halber sei erwähnt, dass ich hier gegen das Don’t Repeat Yourself Principle verstoße. Dies soll lediglich der Vereinfachung im Beispiel dienen.

Ab jetzt muss ich, wenn ein weiterer Status hinzu kommt, lediglich eine Implementierung für diesen machen. Bestehender Code muss nicht mehr angefasst werden.

Strategy Pattern in der Praxis

Heute gibt es von mir einen kleinen Bericht aus der Praxis. Ich habe zunächst folgenden Code geschrieben, welchen ich nun, nachdem die Logik fertig ist, überarbeite.

Ausgangssituation: Anhand diverser Eigenschaften eines Artikels wird dessen Status ermittelt. Abhängig vom Status soll eine Aktion ausgelöst werden. Im vorliegenden Fall ist dies so gelöst, dass eine Switch-Anweisung anhand des Status in den entsprechenden Zweig springt (vgl. ab Zeile 3). In dem jeweiligen Zweig wird eine dedizierte Methode innerhalb der Klasse aufgerufen.

   1: var state = Decide(entry, folder);

   2:  

   3: switch (state)

   4: {

   5:     case SelloutStates.Urgent:

   6:         CreateUrgentGap(result, entry, folder);

   7:         break;

   8:  

   9:     case SelloutStates.Predicted:

  10:         CreatePredictedGap(result,entry,folder);

  11:         break;

  12:  

  13:     case SelloutStates.Verify:

  14:         ForecastNotPossible(result, entry, folder);

  15:         break;

  16:     

  17:     case SelloutStates.AppointmentWarnings:

  18:         CreateAppointmentWarning(result, entry, folder);

  19:         break;

  20:  

  21:     case SelloutStates.Ok:

  22:         CreateUncomplicatedAppointment(result, entry, folder);

  23:         break;

  24:  

  25:     case SelloutStates.Unknown:

  26:         throw new ApplicationException(string.Format("Unbekannter Status für Artikel '{0}'", entry.Article.NumberFormatted));

  27:  

  28:     default:

  29:         throw new ApplicationException(string.Format("Keine Logik zur Verarbeitung des Status für Artikel '{0}'", entry.Article.NumberFormatted));

  30: }

  31:  

  32: SelloutStates Decide(OrderProposalArticleDataEntry entry, OrderProposalFolder folder)

  33: {

  34:     if (folder == null)

  35:         throw new ApplicationException(String.Format("Der Artikel '{0}' war keiner Mappe zugeordnet", entry.Article.NumberFormatted));

  36:  

  37:     if (!entry.AverageUsage.HasValue || !entry.SelloutDate.HasValue)

  38:         return SelloutStates.Verify;

  39:  

  40:     if (!entry.NextIntakeDateAfterSellout.HasValue)

  41:         throw new ApplicationException(String.Format("Bei Artikel '{0}' war kein nächster Wareineingang gesetzt, obwohl ein Leerverkaufsdatum existiert",

  42:             entry.Article.NumberFormatted));

  43:  

  44:     if (entry.SelloutDate < _clock.Now)

  45:         return SelloutStates.Urgent;

  46:  

  47:  

  48:  

  49:     if (entry.SelloutDate < entry.NextIntakeDateAfterSellout)

  50:     {

  51:         var nextPossibleIntake = _clock.Now.AddDays(folder.HandlingTimeInDays).AddDays(folder.DeliveryPeriodInDays);

  52:         var orderInTimeIsPossible = entry.SelloutDate.Value.TimeSpanIgnoringTimeOfDay(nextPossibleIntake).Days >= 0;

  53:  

  54:         return orderInTimeIsPossible ? SelloutStates.AppointmentWarnings : SelloutStates.Predicted;

  55:     }

  56:  

  57:     return SelloutStates.Ok;

  58: }

Ganz offensichtlich wird hier gegen das Open-Closed-Principle und Single-Responsibility-Principle verstoßen, denn wenn ich nun einen weiteren Status aufnehmen möchte, müsste ich das Switch-Statement erweitern und gleichzeitig noch eine weitere Methode einfügen. Übersichtlich sieht das Ganze auch nicht aus.

Deshalb habe ich ein Interface erstellt:

   1: public interface ICreateOrderProposal

   2: {

   3:     void CreateWith(OrderProposals result, OrderProposalArticleDataEntry entry, OrderProposalFolder folder);

   4: }

Für jeden Status habe ich sodann eine Klasse erzeugt, welche das Interface implementiert. Hier ein Beispiel:

   1: public class NoForecastPossible : ICreateOrderProposal

   2: {

   3:     readonly IBuildDefaultProposalsValues _defaultValues;

   4:  

   5:     public NoForecastPossible(IBuildDefaultProposalsValues defaultValues)

   6:     {

   7:         _defaultValues = defaultValues;

   8:     }

   9:  

  10:     public void CreateWith(OrderProposals result, OrderProposalArticleDataEntry entry, OrderProposalFolder folder)

  11:     {

  12:         var appointment = _defaultValues.ForAppointment(entry, folder);

  13:  

  14:         appointment.CalculatedOrderInDays = null;

  15:         appointment.Status = SelloutStates.Verify;

  16:  

  17:         result.Appointments.Add(appointment);

  18:     }

  19: }

 

Zu guter Letzt habe ich mir noch eine Implementierung geschrieben, die in einem Dictionary die Zuordnung von State zu entsprechender Programmlogik hält (ebenfalls als Interface abstrahiert!):

   1: public class StateHandler : IAllocateHandlerToState

   2: {

   3:     private Dictionary<SelloutStates, ICreateOrderProposal> Handler { get; set; }

   4:  

   5:  

   6:     public StateHandler(IClock clock, IBuildDefaultProposalsValues defaultValues)

   7:     {

   8:         Handler = new Dictionary<SelloutStates, ICreateOrderProposal>

   9:                   {

  10:                       {SelloutStates.Oversupply, null},

  11:                       {SelloutStates.Predicted, new PredictedGap(defaultValues)},

  12:                       {SelloutStates.Urgent, new UrgentGap(defaultValues)},

  13:                       {SelloutStates.AppointmentWarnings, new AppointmentWarning(defaultValues, clock)},

  14:                       {SelloutStates.Unknown, null},

  15:                       {SelloutStates.Verify, new NoForecastPossible(defaultValues)},

  16:                       {SelloutStates.Ok, new UncomplicatedAppointment(defaultValues, clock)}

  17:                   };

  18:     }

  19:  

  20:     

  21:     public ICreateOrderProposal Handle(SelloutStates state)

  22:     {

  23:         return Handler[state];

  24:     }

  25: }

Anmerkung: Wie ihr seht verwende ich eine IClock. Das liegt daran, dass ich inzwischen überall im Code statt DateTime.Now immer auf eine Implementierung von IClock zugreife, um Unit Tests zu ermöglichen, die Datumsangaben prüfen.

 

Das Ergebnis ist, dass ich nur noch einen Zweizeiler habe:

   1: var state = _orderProposalState.Evaluate(entry, folder);

   2: _orderProposalFactory.Handle(state).CreateWith(result,entry,folder);

Falls ihr euch wundert, wo die Methode ‘decide’ abgeblieben ist: Diese habe ich im gleichen Zug ebenfalls in eine eigene Klasse extrahiert. Die Implementierung dazu erhalte ich über den Konstruktor vom IoC. Die Variable ‘_orderProposalState’ enthält nun diese Logik.

Vorteil? Ganz offensichtlich ist die Klasse übersichtlicher geworden und zwar erheblich. Natürlich könnte man jetzt argumentieren, dass ich die Logik auf weitere Klassen verteilt habe (was neben dem SRP übrigens auch dem Separation of concerns Prinzip entspricht). Das ist natürlich richtig, aber in dieser Klasse sehe ich auf Anhieb den Zusammenhang: Ich ermittle einen Status eines Artikels. Im Anschluss reagiere ich in einer speziell dafür vorgesehenen Geschäftslogik. Wenn ich nun wissen will, wie der Status ermittelt wird, dann gehe ich in die dafür zuständige Klasse. Will ich hingegen wissen, wie ein Artikel mit diesem speziellen Status verarbeitet wird, gehe ich in die dedizierte Klasse. Will ich das Ganze erweitern, dann baue ich einfach einen weiteren entsprechenden Handler. Bestehender Code muss nicht angefasst werden (Ausnahme: Die Klasse StateHandler, aber auch dies könnte man anders lösen!). Und zu guter Letzt gestalten sich die Tests deutlich einfacher.

Clean Code Sample

In einem Gespräch mit unseren Webentwicklern habe ich den Kollegen einen Vorschlag für die Implementierung des Zugangs zu unserem Kundenportals unterbreitet: Unsere PHP Entwickler sind inzwischen ebenfalls auf den Ansatz des Testens umgestiegen, sodass sie ihre PHP und JavaScript Architektur anpassen müssen. Leider fehlt es an Frameworks, die einem ein Teil der Arbeit abnehmen könnten, wie z.B. Mocking Frameworks a la FakeItEasy. Falls jemand Frameworks kennt, dann melde er sich bitte bei mir.

Anforderungen (vereinfacht): An unserem Kundenportal ‘MyHeco’ soll man sich einloggen, ausloggen und das Password zurücksetzen können. Die Webanwendung konsumiert dabei .NET basierende Webservices!

Dies soll nun möglichst clean unter Einhaltung gängiger Prinzipien (wie z.B. dem Single Responsibility Principle) umgesetzt werden. Wie bereits erwähnt, muss die Architektur so gewählt werden, dass der Code einfach zu testen ist. Das ist v.a. auch deswegen nicht ganz trivial, weil Aufrufe nach ‘Außen’ zu Webservices gehen.

Hier also mein Vorschlag (natürlich in C# formuliert):

   1: interface IAuthenticateMyHecoUser

   2: {

   3:     MyHecoUser Login(string email, string password);

   4: }

   5:  

   6: interface ILogoutMyHecoUser

   7: {

   8:     void Logout(string session);

   9: }

  10:  

  11: interface IResetMyHecoPassword

  12: {

  13:     void ResetPassword(string email);

  14: }

Schreibe 3 Interfaces für die eigentlichen Aktionen, z.B. Login. Natürlich könnte dafür auch nur ein Interface geschrieben werden werden, allerdings empfinde ich es so als sauberer und die Interfaces lassen sich sprechend benennen (ohne Weasel Words, also Füllwörter; tollen Artikel dazu gibt es von Johannes).

Danach benötigen wir ein Interface für die Webservice-Kommunikation, welches ich an dieser Stelle leer lasse:

   1: public interface IERPCommunication

   2: {

   3:      

   4: }

Was ich ebenfalls zu den Kontrakten zähle (vergleicht dazu diesen Artikel), ist das DTO MyHecoUser, welches später die gefüllten Daten zum Austausch innerhalb von PHP enthalten soll.

   1: public class MyHecoUser

   2: {

   3:     public readonly int Kundennummer;

   4:     public readonly string Session;

   5:     //und diverse weitere

   6: }

Zu guter Letzt benötigen wir noch die Implementierung, die so aussehen könnte. Je nach Umfang und Größe des Codes, wäre es auch durchaus denkbar, die Implementierung auf verschiedene Klassen aufzusplitten. Aber in diesem Fall implementiert eine Klasse die 3 Interfaces aus dem Code Snippet ganz oben.

   1: public class MyHeco : IAuthenticateMyHecoUser, ILogoutMyHecoUser, IResetMyHecoPassword

   2: {

   3:     readonly IERPCommunication _webservice;

   4:  

   5:     public MyHeco(IERPCommunication webservice)

   6:     {

   7:         _webservice = webservice;

   8:     }

   9:  

  10:     public MyHecoUser Login(string email, string password)

  11:     {

  12:         throw new System.NotImplementedException();

  13:     }

  14:  

  15:     public void Logout(string session)

  16:     {

  17:         throw new System.NotImplementedException();

  18:     }

  19:  

  20:     public void ResetPassword(string email)

  21:     {

  22:         throw new System.NotImplementedException();

  23:     }

  24: }

 

Durch die Abstraktion der Webservice-Kommunikation hinter einem Interface und dem Injizieren im Konstruktor, können die Kollegen nun für den Test einen selbstgeschriebenen Fake hineinreichen. Ich validiere hier zugegebenermaßen den Parameter nicht, da ich in unserer Architektur auf IoC mit Castle.Windsor setze. Die Benennung spricht mich persönlich auch an, da sie sehr aussagekräftig ist:

MyHeco.Login oder Myheco.Logout sind eindeutig. Die Kohäsion scheint mir ebenfalls recht hoch, da die 3 möglichen von der Fachabteilung definierten MyHeco Aktionen in einer Klasse gekapselt sind. Trotzdem wäre eine spätere Auftrennung sehr einfach, da in der konsumierenden Klasse nur das konkrete Interface reingegeben wird. Beispiel: In einer Bestellung muss sich der Kunde erst einloggen. Also bekommt die Logik für eine Bestellung im Konstruktor ein IAuthenticateMyHecoUser reingereicht. Ist in Zukunft die Implementierung nicht mehr in der Klasse MyHeco, ändert sich die Logik der Bestellung in keiner Weise.

Arithmetische Operationen – Das wichtige Schlüsselwort checked

 

Was steht nach der Ausführung von Zeile 2 in der Variablen ‘input’?

   1: var number1 = int.MaxValue;

   2: var input =  number1 + 10;

Nein, die Antwort lautet nicht, dass eine Exception geschmissen wird und nein, es ist auch nicht int.MaxValue.

Das Ergebnis ist int.MinValue + 9

image

Und wie müsste der Code geändert werden, damit die Operation eine StackOverflowException schmeißt? Die Lösung ist trivial, allerdings kennen die meisten das Schlüsselwort checked nicht. Deshalb sei an dieser Stelle auf darauf verwiesen.

Noch ein weiterer Hinweis:

   1: var zero = 0;

   2: Console.WriteLine(1d / zero);

   3: Console.WriteLine(1 / zero);

Während Zeile 2 als Ergebnis unendlich liefert, erfolgt in Zeile 3 eine Exception.

Integration eines SharePoint Wikis ins ERP-System

Seit ca. 3 Monaten setzen wir erfolgreich auf die Kollaborationsmöglichkeiten, die ein Wiki-System bietet. Als Basis verwenden wir dazu die SharePoint Foundation, welche kostenlos für Windows Server zur Verfügung steht. In dem folgenden Video zeige ich, worauf es zu achten gilt und wie die Firma heco den Wiki Inhalt erfolgreich in das hauseigene ERP-System integriert hat.

In dem Video war leider keine Zeit mehr, um auf weitere Ideen einzugehen und weiterführende Quellen zu nennen, deshalb hole ich dies hiermit nach:

  • Geplant ist eine Ausweitung um Multimediainhalte, allerdings nicht nur aus SharePoint selbst, sondern auch von YouTube
  • Das Aufrufen von ERP-Funktionalität aus dem Wiki
  • Ein Webpart, welches automatisch die Daten des für den Inhalt zuständigen Sachbearbeiters lädt und anzeigt, z.B. die Telefonnummer

Für Rückfragen stehe ich gerne über die üblichen Kommunikationskanäle zur Verfügung. Aber auch wir haben noch Fragen: Kann mir jemand sagen, wo ich das Template für eine Wiki Seite anpassen kann, um z.B. auf allen Seiten immer den letzten Bearbeiter anzuzeigen oder um diverse Links immer am unteren Rand einzublenden?

 

Weiterführende Links:

%d Bloggern gefällt das: