Open Space Rules

Am letzten Wochenende war in Karlsruhe der zweite .NET Open Space Süd. Im Herzen der Stadt haben sich um die 50 Entwickler, Architekten, Leute aus anderen IT-Bereiche getroffen, um all die Themen zu besprechen, worüber wir während des Jahres gegrübelt haben oder wofür uns die Zeit einfach gefehlt hat. Wir haben uns über Softwarezellen, Monaden, BDD, F#, Spieleentwicklung, Usability, Scrum und vieles mehr unterhalten.

Ich kann die vielen Eindrücke, die ich mitgenommen habe, nicht mal annähernd aufzählen, dafür muss man selbst teilnehmen. Björn hat es ganz genau zusammengefasst:

To sum it up in 5 words: The event was a blast. I had an unbelievable amount of fun coding, chatting & learning with some of smartest people in the German .NET Community.

Was die Sessions betrifft, und dazu zähle ich auch die unglaublich interessanten Gespräche am Samstag Abend bei einem Glas Wein, möchte ich nur zwei erwähnen. Eine davon behandelte das Thema “DDD” und für mich war dies die dritte Session in den letzten 12 Monaten. Bei der ersten zwei Sessions – in Ulm vor einem Jahr und in Leipzig in Oktober – standen mehr Fragen als Antworten im Raum, für viele war das Thema absolut “exotisch”. Diesmal waren wir alle von der Idee überzeugt, die Frage war nicht mehr ob, sondern nur noch wie wir damit umgehen sollen. Eine unglaubliche Entwicklung, domain-driven design ist in den Köpfen angekommen.

Das zweite Thema hat für ein paar von uns, u.a. Sergey, Albert, Ilker, Kai, Kostja, Daniel, Philipp, Gregor und ich den halben Samstag Abend gefüllt, und war ein Thema, das Ralf bereits in Leipzig angesprochen hat: wie ist das Berufsbild eines Softwareentwicklers und vor allem, wie wird es in 5 Jahren sein? In Leipzig hieß zwar die Frage “Wie machen wir unseren Beruf attraktiver”, in dieser Kneipe in Karlsruhe hatten wir endlich eine Antwort darauf gefunden. Kurz zusammengefasst: die Zeit des “embedded programmer”-s – der Hacker aus dem Keller, der mit seiner Hardware verschmolzen ist – ist vorbei, wir leben in dem Zeitalter des kommunikativen agilen Teams, das sich aus Fachleute aus allen Bereichen zusammensetzt. Kennt ihr noch eine andere Spezies, die den Job wählt bloß um dazu zu lernen, um an der Arbeit Spaß zu haben – und dafür auch noch Geld bekommt? Andere gehen von 8 bis 17 Uhr arbeiten, um Geld zu verdienen. Wir wollen allerdings viel mehr, wir müssen unsere Sucht nach “das neueste Feature ausprobieren, das gerade gelesene Muster anwenden” stillen. Und das ist warum, wir von der Arbeit nach Hause gehen, um dort in Ruhe weiterarbeiten zu können 🙂 Das ist nämlich für Nerds wie wir, keine Arbeit, es ist ein riesen Spaß.

Wir haben sogar einen empirischen Versuch gestartet, die Jungs haben ihren Status bei Facebook in “I know how Facebook works” geändert, um zu sehen, wie viele weibliche Reaktionen sie darauf bekommen 😉 Der Abend hat auch einen anderen Beweis für unseren Nerd-sein geliefert, wie Kai es irgendwann bemerkt hat: wer sitzt noch über eine Stunde, nachdem er gezahlt hat, in einer Kneipe, und trinkt nicht, nur um zu reden und reden und reden? Nur ein Softwarekünstler!

Ich war schon letztes Jahr von Open Space überzeugt, diese Veranstalltung hat dies nochmals bestätigt: Open Space rules. Vielen Dank noch mal an den Organisatoren, ich kann nur wiederholen, was ich schon mal gesagt habe: Chapeau! Wenn ihr ruft, wir kommen 🙂

Kontextabhängige Datenvalidierung

Die Idee stammt von Jimmy Nilsson – Applying Domain-Driven Design and Patterns. Er hat nach einer Möglichkeit gesucht, die immer wiederkehrende Aufgabe, Daten zu validieren, flexibel und kontextabhängig zu gestalten, und zwar so, dass man es nur einmal schreiben muss.

Wie läuft normalerweise so eine Validierung ab? Man will wissen, ob eine Instanz als solche allen Vorschriften entspricht, und wenn nicht, dann welche Felder passen nicht. Jeder, der jemals Webanwendungen geschrieben hat, weiß, wie mühsam und langweilig es ist, jeden Eingabewert auf Gültigkeit zu testen. (Hier geht allerdings nicht unbedingt um Formulare, da kann man ja die Validierung z.B. bei ASP.MVC 2.0 mit Data Annotations durchführen.)

Also zurück zu den Anforderungen: um die verschiedenen Regeln wiederverwendbar zu machen, braucht man diese von einem Interface abzuleiten:

namespace ValidationFramework
{
    public interface IRule
    {
        bool IsValid { get; }
        int IdRule { get; }
        string[] BooleanFieldsThatMustBeTrue { get; }
        string Message { get; }
    }
}

IsValid sagt aus, ob der Regel verletzt wurde. Die IdRule ist dafür da, um diese Regeln einfacher identifizieren zu können. BooleanFieldsThatMustBeTrue kann dafür verwendet werden, um Vorbedingungen zu prüfen. Message braucht wohl keine Erklärung.
Jetzt kann man verschiedene Regeln und eine Basisklasse für gemeinsame Funktionalitäten definieren:

using System.Collections.Generic;
using System.Linq;
using System;

namespace ValidationFramework
{
    public abstract class RuleBase : IRule
    {
        private readonly object m_value;
        private readonly int m_idRule;
        private readonly string[] m_booleanFieldsThatMustBeTrue;
        private readonly object m_holder;

        protected RuleBase(int idRule, string[] fieldsConcerned, string fieldname, object holder)
        {
            m_value = GetPropertyValue(holder, fieldname);
            m_booleanFieldsThatMustBeTrue = fieldsConcerned;
            m_holder = holder;
            m_idRule = idRule;
        }

        private static object GetPropertyValue(object holder, string fieldname)
        {
            return holder.GetType().GetProperty(fieldname).GetValue(holder, null);
        }

        protected object GetValue()
        {
            return m_value;
        }
        protected object GetHolder()
        {
            return m_holder;
        }

        public abstract bool IsValid { get; }
        public int IdRule
        {
            get { return m_idRule; }
        }

        public string[] BooleanFieldsThatMustBeTrue
        {
            get { return m_booleanFieldsThatMustBeTrue; }
        }

        public abstract string Message { get; }

        protected bool BooleanFieldsConcernedAreTrue()
        {
            return
                m_booleanFieldsThatMustBeTrue.Select(a => (bool)m_holder.GetType().GetProperty(a).GetValue(m_holder, null)).
                    Select(b => b).Count() == m_booleanFieldsThatMustBeTrue.Length;
        }
    }

    public class DateIsInRangeRule : RuleBase
    {
        private readonly DateTime m_minDate;
        private readonly DateTime m_maxDate;

        public DateIsInRangeRule(DateTime minDate, DateTime maxDate, int idRule, string fieldName, object holder) : base( idRule, null,fieldName, holder)
        {
            m_minDate = minDate;
            m_maxDate = maxDate;
        }

        public override bool IsValid
        {
            get { 
                var value = (DateTime) GetValue();
                return value >= m_minDate && value <= m_maxDate;
            }
        }

        public override string Message
        {
            get {
                return IsValid
                           ? string.Empty
                           : string.Format("Das Datum ist nicht in gültigen Bereich: {0}-{1}", m_minDate, m_maxDate); }
        }
    }

    public class MaxStringLengthRule : RuleBase
    {
        private readonly int m_maxLength;

        public MaxStringLengthRule(int maxLength, int idRule, string fieldname, object holder) : base(idRule, null, fieldname, holder)
        {
            m_maxLength = maxLength;
        }

        public override bool IsValid
        {
            get { return GetValue().ToString().Length <= m_maxLength; }
        }

        public override string Message
        {
            get {
                return IsValid
                           ? string.Empty
                           : string.Format("Die zugelassene Länge von {0} Zeichen wurde überschritten",m_maxLength); }
        }
    }
}

Jetzt, da die Grundlagen stehen, schauen wir mal, wie man die Validierungsregeln festlegen würde.
Hier ist eine ganz einfache Beispielklasse:

using System;

namespace ValidationFramework
{
    public class Account
    {
        public Account()
        {
            Created = DateTime.Now;
            Address = string.Empty;
        }

        public DateTime Created { set; get; }
        public string Address { get; set; }
        public bool Activated { get; set; }
    }
}

Jetzt definieren wir die Regeln, wonach eine Instanz valide ist oder nicht. Die neue Eigenschaft IsValidated soll diese Information speichern.

    public class Account
    {
        private readonly IList<IRule> m_validationRules = new List<IRule>();

        public Account()
        {
            Created = DateTime.Now;
            Address = string.Empty;
        }

        public DateTime Created { set; get; }
        public string Address { get; set; }
        public bool Activated { get; set; }
        public bool IsValidated { get; set; }

        private void SetupValidationRules()
        {
            m_validationRules.Add(new DateIsInRangeRule(new DateTime(1990,1,1),DateTime.Now,1,"Created",this ));
            m_validationRules.Add(new MaxStringLengthRule(10,2,"Address",this));
        }
    }

Um Regeln auch dynamisch setzen zu können, wird eine Methode AddValidationRule(IRule) definiert

 
    public class Account
    {
...    
        public void AddValidationRule(IRule rule)
        {
            m_validationRules.Add(rule);
        }
...
    }

Nun müssen wir diese Regeln nur noch auswerten. Dafür wird in RuleBase eine statische Methode definiert und in der Beispielklasse die Methode IEnumerable GetBrokenRules()

    public abstract class RuleBase : IRule
    {
...
        public static IEnumerable<IRule> CollectBrokenRules(IList<IRule> rulesToCheck)
        {
            return rulesToCheck.Where(a => !a.IsValid).Select(a => a);
        }
    }
    public class Account
    {
...
        public IEnumerable<IRule> GetBrokenRules()
        {
            SetupValidationRules();
            return RuleBase.CollectBrokenRules(m_validationRules);
        }
...
    }

Um zu beweisen, dass es funktioniert, hier ein Paar Tests:

using System;
using System.Linq;
using NUnit.Framework;

namespace ValidationFramework.Tests
{
    [TestFixture]
    [Category("DateIsInRangeRule")]
    public class If_the_CreationDate_of_the_Account_is_in_range
    {
        [Test]
        public void Then_the_property_Created_is_valid()
        {
            var sut = new Account { Created = DateTime.Now.AddYears(-1) };
            Assert.That(sut.GetBrokenRules().Count(),Is.EqualTo(0));
        }
    }
    [TestFixture]
    [Category("DateIsInRangeRule")]
    public class If_the_CreationDate_of_the_Account_is_to_old
    {
        [Test]
        public void Then_the_property_Created_is_not_valid()
        {
            var sut = new Account { Created = new DateTime(1989,1,1)};
            Assert.That(sut.GetBrokenRules().Count(), Is.EqualTo(1));
            Assert.That(sut.GetBrokenRules().First().IdRule, Is.EqualTo(1));
        }
    }

    [TestFixture]
    [Category("MaxStringLengthRule")]
    public class If_the_Address_of_the_Account_is_to_long
    {
        [Test]
        public void Then_the_property_Address_is_not_valid()
        {
            var sut = new Account { Address = "12345678901"};
            Assert.That(sut.GetBrokenRules().Count(), Is.EqualTo(1));
            Assert.That(sut.GetBrokenRules().First().IdRule,Is.EqualTo(2));
        }
    }
}

Mit den vorhandenen Tests kann man nun refaktorisieren um die Prinzipien der Separation Of Concern einzuhalten. Außerdem ist nun Zeit, auch über die Kontextbezogenheit nachzudenken.
Die Klasse Account wird wahrscheinlich durch irgendeinen ORMapper gefüllt und braucht auf keinem Fall die Verantwortung, die Validierungsregeln zu verwalten. Deshalb kann man das Specification-Pattern von DDD anwenden, und diese Regeln in so eine Spezifikation verschieben:

using System;
using System.Collections.Generic;

namespace ValidationFramework
{
    public interface IValidationSpecification
    {
        IList<IRule> GetValidationRules();
    }

    public class AccountValidationSpecification : IValidationSpecification
    {
        private readonly Account m_objectToValidate;

        public AccountValidationSpecification(object objectToValidate)
        {
            m_objectToValidate = (Account) objectToValidate;
        }

        public IList<IRule> GetValidationRules()
        {
            return new List<IRule>
                       {
                           new DateIsInRangeRule(new DateTime(1990, 1, 1), DateTime.Now, 1, "Created",
                                                 m_objectToValidate),
                           new MaxStringLengthRule(10, 2, "Address", m_objectToValidate)
                       };
        }
    }
}

Die Spezifikationen werden selbstverständlich von einer Factory geliefert und sie werden per Setter Injection gesetzt.

    public  class ValidationSpecificationFactory
    {
        public static IValidationSpecification Create<T>(object objectToValidate)
        {
            if (typeof(T) == typeof(Account))
                return new AccountValidationSpecification(objectToValidate);
            throw new NotSupportedException();
        }
    }
    public class Account
    {
...
        public void SetValidationSpecification(IValidationSpecification specification)
        {
            foreach (IRule rule in specification.GetValidationRules())
                m_validationRules.Add(rule);
        }
...
    }
//Der Aufruf ist dann
var account = new Account();
account.SetValidationSpecification(ValidationSpecificationFactory.Create<Account>(account));

Dadurch ist die Bedingung, Kontextabhängige Validierungsregeln festlegen zu können, erfüllt.

Man kann generische Spezifikationen definieren, die die Regeln für verschiedene Zwecke, z.B. für Persistieren oder auch einfach nur für Akzeptieren definieren:

   
    public interface IValidationSpecification
    {
        IList<IRule> GetValidationRules();
        IList<IRule> GetValidationRulesRegardingPersistence();
    }

Hier der komplette Quellcode zum Herunterladen.

NOS Sued kommt: Ulm heißt heuer Karlsruhe

.NET Open Space Süd
Es ist wieder soweit, die Anmeldungen für .NET Open Space Süd haben begonnen. Ich war letztes Jahr in Ulm und es war großartig! Wir haben wahnsinnig viel gelernt und super Leute getroffen. Es stand schon damals außer Frage, ob ich wieder hingehe. Irgendwann in den letzten 2 Wochen kam dann endlich die freudige Nachricht: Ulm ist dieses Jahr in Karlsruhe 🙂
Also kommt hin und werdet Teil der Community, ihr werdet es nicht bereuen!

P.S.: wenn ich die Teilnehmerliste anschaue, muss ich die Frage vom letzten Jahr wiederholen: gibt es keine Entwicklerinnen in diesem Teil des Landes?