Peter Bucher Ralf Westphal

Blog

Nutze den Augenblick
und teile der Welt mit, was Du zu sagen hast.

LightCore

Freitag, 1. Januar 2010, 00:00 Uhr
Permalink | Kommentare (1) | Kommentare als RSSRSS

Vor ungefähr neun Monaten habe ich in meinem Blogeintrag Microkernel im Eigenbau beschrieben, wie mit wenigen Zeilen ein eigener Microkernel unter .NET realisiert werden kann. Auch wenn diese Variante durchaus für einfache Szenarien genügt, bestehen in der Praxis häufig weitere Anforderungen.

Da in der Regel kein eigener Microkernel entwickelt werden soll, bleibt die Wahl der Qual, welcher Microkernel zum einen die eigenen Anforderungen erfüllt und zum anderen aber zugleich dem persönlichen Gusto am ehesten entspricht. Erschwert wird die Wahl dabei durch die Vielzahl der verfügbaren Systeme.

Wie viele andere Entwickler konnte auch ich mich in der Vergangenheit nicht eindeutig für ein einziges System entscheiden:

  • So hat mir autofac zwar mit seinem an C# 3.0 angelehnten Programmiermodell mit umfangreicher Nutzung von Lambdaausdrücken zugesagt, nicht jedoch von der Performance.
  • Unity hingegen gefällt mir ebenfalls von dem verwendeten Programmiermodell, jedoch habe ich mit den Microsoft Application Blocks in der Vergangenheit keine all zu guten Erfahrungen gemacht, weshalb ich bei Unity sehr skeptisch bin.
  • Spring.NET ist als Framework empfehlenswert, wenn nicht nur ein Microkernel, sondern auch Logging, O/R-Mapping, aspektorientierte Funktionen und ähnliches gewünscht werden – für alles andere ist es jedoch zu umfangreich und komplex.

Diese Liste könnte ich noch problemlos fortsetzen und um einige weitere Microkernel ergänzen.

Mit LightCore hat Peter Bucher nun einen weiteren Microkernel entwickelt, der zudem als Dependency Injection-Container dient. LightCore ist vollständig in C# geschrieben, folgt dabei den Richtlinien der Clean Code Developer-Initiative und ist weitestgehend mit Unittests abgedeckt.

Für sich genommen wecken diese Aspekte durchaus Interesse an dem zu Grunde liegenden Code – doch wirklich interessant wird LightCore durch seine Eigenschaften beziehungsweise Fähigkeiten, die LightCore von anderen Microkerneln abheben:

  • LightCore ist leichtgewichtig und besteht im Wesentlichen aus einer 27 KByte großen Assembly. Je nach Anforderungen kommen noch einige Assemblies hinzu, doch über 50 KByte wächst der Platzbedarf in keinem Fall.
  • LightCore ist verdammt schnell: Im Vergleich zu nativem .NET-Code ist die Erzeugung von 100.000 Objektinstanzen in LightCore lediglich um 7% langsamer – autofac bedarf dafür hingegen ein Vielfaches der Zeit.
  • LightCore ist einfach und flexibel zu konfigurieren: Zum einen kann die Konfiguration vollständig im Code erfolgen, zum anderen wird XAML als Konfigurationssprache unterstützt, wobei nahezu beliebige Datenquellen genutzt werden können.
  • LightCore unterstützt die Instanziierung von Objekten nicht nur per Reflection, sondern auch per Lambdaausdruck, wodurch die Performance im Vergleich zu der Reflection-basierten Variante deutlich zunimmt.
  • LightCore kann mit verschiedenen Lebenszyklen von Objekten umgehen: Transiente Objekte können dadurch ebenso erzeugt werden die Singletons, threadaffine Singletons und HttpRequest-bezogene Objekte.
  • LightCore ermöglicht die Registrierung von Typen an Hand eines Kontrakts, eines Namens oder einer Klasse. Zusätzlich können Argumente spezifziert werden, die während der Instanziierung an den jeweiligen Konstruktor übergeben werden.
  • LightCore unterstützt die Gruppierung von Registrierungen, so dass auf einfache Art zwischen verschiedenen Konfigurationen von Registrierungen gewechselt werden kann.
  • LightCore integriert sich in klassisches ASP.NET und ASP.NET MVC, so dass Dependency Injection auch in diesen Technologien ohne zusätzlichen Aufwand genutzt werden kann.
  • LightCore unterstützt das CommonServiceLocator-Projekt, das seinerseits eine Abstraktion über verschiedene Microkernel ermöglicht.

Dieser umfangreichen und bemerkenswerten Featureliste zum Trotz verfügt LightCore über ein ausgesprochen schlichtes API, so dass zur tatsächlichen Verwendung nur einige wenige Zeilen Code erforderlich sind.

Alles in allem ist LightCore also eine kompakte, performante, flexible und ausgereifte Komponente, die für meine Projekte zukünftig die Antwort auf die Frage nach einem geeigneten Microkernel darstellt.

Für die Zukunft wünsche ich LightCore daher alles Gute.

Was gehört in den Businesslayer?

Montag, 17. August 2009, 20:31 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Vor ziemlich genau einem Jahr wurde ich von Peter Bucher mit der Frage Was ist Architektur? konfontriert. Eine vernünftige und fundierte Antrwort auf diese zunächst einfach erscheinende Frage geben zu können, hat mich damals einige Wochen in Anspruch genommen.

Vorgestern hat mir Peter nun wieder eine solche Frage gestellt, deren Beantwortung aus dem Stegreif zumindest nicht ganz trivial ist:

Was gehört in den Businesslayer?

Des weiteren ergänzt Peter seine Frage um eine Erklärung, inwiefern er überhaupt Diskussionsbedarf sieht:

[…] alles was Redundanzen im Datalayer verursacht (da es mehrere geben kann), gehört dort hin.

Aber ich höre und lese so viel von Geschäftslogik / Businesslogik und verstehe darunter irgendwelche Logik, wie beispielsweise Preisberechung oder Algorithmen die ausgeführt werden müssen.

Auf jeden Fall habe ich noch nie mehr als Validation / Caching und Logging in einem Businesslayer gesehen. Meistens ist es nur Delegation oder die Transformation von einer DataRow zu einem Objekt […]

Unsere – bislang gemeinsam genutzte Sichtweise – war, dass in die Businesslogik all jenes gehört, was weder in die UI noch in die Datenzugriffsschicht gehört. Allerdings ist eine solche Negativdefinition an Hand einer Exklusion nicht besonders tragfähig: Eine Definition an Hand charakteristischer Merkmale des zu inkludierenden Codes wäre bedeutend nützlicher.

Interessant finde ich, dass die von Peter angesprochenen Aspekte Validierung, Caching und Logging zwar durchaus im Businesslayer zu finden sind, allerdings weder explizite Businesslogik darstellen, noch auf diesen beschränkt wären: So kann sich Validierung durchaus auch zusätzlich in der UI und der Datenzugriffsschicht befinden. Gleiches gilt für die beiden anderen Aspekte Caching und Logging.

Schließlich spricht Peter noch die Transformation und Delegation zwischen Objekten der Datenbank und Objekten des Objektmodells an: Korrekt ist, dass solcher Code häufig im Businesslayer enthalten ist – ob dies jedoch die geeignete Stelle für solchen Code ist, wage ich zu bezweifeln.

Wie also würde ich Peters Frage beantworten?

Ausgehend von meiner Argumentation in Was ist Architektur? verfügt eine Anwendung über einen Sinn: Eine Anwendung wird geschrieben, um ein Problem zu lösen. Bezogen auf diesen Sinn sind die beiden Elemente UI und Datenzugriffsschicht zwar notwendig, aber nicht wesentlich, denn im Gegensatz zum Kern der Anwendung sind sie letztlich beliebig austauschbar.

Es spielt keine Rolle für die Lösung des zu Grunde liegenden Problems, ob die UI in WPF, in Silverlight oder in einer schlichten Konsole umgesetzt wurde – ebenso wie es keine Rolle spielt, ob die Daten in einem SQL Server oder einer schlichten XML-Datei abgelegt werden.

Der Kern einer Anwendung hingegen ist unverändlich. Würde man diesen grundlegend verändern, so würde damit implizit ein anderes als das ursprüngliche Problem gelöst; Die Anwendung erhielte einen anderen Sinn.

Insofern kann man den Businesslayer also mit dem Kern der Anwendung gleichsetzen, und der Kern enthält zunächst einmal sämtliche anwendungsbezogene Logik – eben die sogenannte Businesslogik. Diese kann in Form von komplexen Workflows definiert sein, aber auch ganz schlicht und rudimentär in Form einiger weniger if-Abfragen.

Zunächst kann man also festhalten, dass ich mit Peters Formulierung

Aber ich höre und lese so viel von Geschäftslogik / Businesslogik und verstehe darunter irgendwelche Logik, wie beispielsweise Preisberechung oder Algorithmen die ausgeführt werden müssen.

einverstanden bin. Doch warum sieht man so wenig von dieser Logik?

Dies liegt schlichtweg darin begründet, dass viele der gängigen kleineren und mittleren Anwendungen – insbesondere im Web – keine ausgefeilte Logik benötigen! Zahlreiche dieser Anwendungen begnügen sich nämlich mit den üblichen CRUD-Funktionen: Im Grunde stellen sie nur eine visualisierte Schnittstelle zur Datenbank dar, und verfügen daher über keine dedizierten Workflows.

Und die wenigen Workflows, die vorhanden sind, nimmt man dann neben dem Berg an CRUD-Funktionen nicht mehr als eigenständiges Regelwerk wahr.

Auf der anderen Seite führt ein derart minimalistischer Businesslayer dann dazu, dass Aspekte wie Validierung – die im Grunde gar keine Businesslogik darstellen – ebenfalls dort angesiedelt werden. Schließlicht macht man sich über eine weitere Unterteilung des Businesslayers gar keine Gedanken mehr – dieser ist ja bereits so klein.

Der Aufwand, einen übergreifend zur Verfügung stehenden dedizierten Servicelayer zu entwickeln, wird dann gerne gescheut – obwohl er gegebenenfalls die bessere Wahl wäre.

Interessanterweise gibt es auch Aspekte, die durchaus in den Businesslayer gehören, und dort häufig nicht angesiedelt werden: Hierzu zählt beispielsweise – und das mag verwundern – das Transaktionsmanagement. Transaktionen sind schließlich nicht per se kurzlebiger Natur und auf die Datenbank bezogen.

Schlussendlich möchte ich Peter also nicht nur recht geben, wenn er den Businesslayer mit Anwendungslogik gleichsetzt, sondern ich möchte auch eine Lanze dafür brechen, dem Businesslayer an sich mehr Aufmerksamkeit zu schenken, und auch mehr darüber nachzudenken, ob eine weitere Unterteilung des Businesslayers in die eigentliche Logik und weitere Komponenten nicht durchaus Sinn ergibt.

Microkernel im Eigenbau

Freitag, 13. März 2009, 21:17 Uhr
Permalink | Kommentare (9) | Kommentare als RSSRSS

Ende November des vergangenen Jahres habe ich in Architektur lernen empfohlen, sich mit dem Einsatz eines Microkernels zu beschäftigen:

Auch dieses Thema fördert das Denken in Schnittstellen, allerdings auf einer anderen Ebene als die Entwurfsmuster - beziehen diese sich nämlich auf das Klassendesign, bezieht sich der Einsatz eines Microkernels auf das Komponentendesign einer Anwendung.

Außerdem habe ich behauptet, dass es allen bestehenden Microkerneln zum Trotz nicht schwierig sei, einen einfachen Microkernel selbst zu implementieren:

Zwar gibt es einige Projekte wie beispielsweise Unity, die man sich näher anschauen kann, allerdings ist ein einfacher Microkernel in wenigen Zeilen selbst implementiert, wobei der Lerneffekt dann allerdings weitaus größer ist.

Da ich seither immer wieder nach einem Beispiel gefragt werde, beschreibe ich nun hier, wie man einen eigenen Microkernel erstellt. Ich werde mich dabei auf das wesentliche Feature – nämlich die Instanziierung eines konkreten Typen an Hand eines gegebenen Contracts – beschränken, und Aspekte wie Fehlerbehandlung, Performance, Sicherheit und Konfigurierbarkeit außen vor lassen.

Letztlich sind folgende Schritte nötig, um einen eigenen lauffähigen Microkernel zu entwickeln:

  • Erzeugen einer Konfiguration, die das Mapping von Contracts auf konkrete Typen übernimmt. Es bietet sich an, diese Konfiguration in einer XML-Datei abzulegen, da diese erstens dank LINQ to XML mit wenig Aufwand ausgelesen werden kann, und dies zum anderen ermöglicht, die Konfiguration des Microkernels zu ändern, ohne das Projekt neu kompilieren zu müssen.
  • Instanziieren des für einen gegebenen Contract definierten konkreten Typs. Dies geschieht im Wesentlichen mit Hilfe der Activator-Klasse von .NET, wobei so wohl in das Projekt integrierte Assemblies wie auch zusätzliche Assemblies unterstützt werden sollen.

Zunächst muss also eine Konfiguration erzeugt werden, die das Mapping von einem Contract auf einen konkreten Typen übernimmt. Dazu dient folgende Struktur, die das Grundgerüst einer entsprechenden XML-Datei darstellt:

<?xml version="1.0" encoding="utf-8" ?>
<serviceLocator>
  <mappings>
    <mapping contract="" type="" />
  </mappings>
</serviceLocator>

Als einfaches Beispiel soll eine Instanz des Typs MemoryStream im Namensraum System.IO verwendet und auf die Schnittstelle IDisposable gemappt werden.

Hierzu müssen lediglich die Namen der beiden Typen in der XML-Datei angegeben werden, wobei diese vollqualifiziert  – also einschließlich des jeweiligen Namensraums – angegeben werden müssen:

<?xml version="1.0" encoding="utf-8" ?>
<serviceLocator>
  <mappings>
    <mapping contract="System.IDisposable" type="System.IO.MemoryStream" />
  </mappings>
</serviceLocator>

Im nächsten Schritt wird eine statische Klasse ServiceLocator erzeugt, die über die folgenden Methoden verfügt:

  • Einen statischen Konstruktur: Dieser ist dafür zuständig, die Konfiguration aus der XML-Datei auszulesen, und die dort angegebenen Namen der Contracts und Typen in jeweils eine Instanz der Type-Klasse zu überführen, und diese Zuordnungen danach in einem Dictionary abzuspeichern.
  • Eine statische Methode namens GetService: Diese Methode hat als einzige Aufgabe, den konkreten Typ für den angeforderten Contract aus dem Dictionary zu ermitteln, diesen zu instanziieren und entsprechend gecastet an den Aufrufer zurückzugeben.

Insgesamt ergibt sich daraus die folgende Grundstruktur für die Klasse ServiceLocator:

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

namespace CherryFlavored.NET.Core.Kernel
{
    /// <summary>
    /// Represents a service locator.
    /// </summary>
    public static class ServiceLocator
    {
        /// <summary>
        /// Contains the mappings from contracts to types.
        /// </summary>
        private static Dictionary<Type, Type> _mappings;

        /// <summary>
        /// Initializes the <see cref="ServiceLocator" /> type.
        /// </summary>
        static ServiceLocator()
        {
        }

        /// <summary>
        /// Gets an instance for the specified contract.
        /// </summary>
        /// <typeparam name="TContract">The type of the contract.</typeparam>
        /// <returns>An instance that matches the specified contract.</returns>
        public static TContract GetService<TContract>() where TContract : class
        {
        }
    }
}

Um die Konfigurationsdatei auszulesen und das Dictionary entsprechend zu erzeugen, bietet sich – wie oben bereits erwähnt – die Verwendung von LINQ to XML an:

// Load the configuration.
_mappings =
    (from mapping in
         XElement.Load("ServiceLocator.xml").Element("mappings").Elements("mapping")
     select
         new KeyValuePair<Type, Type>(
             Type.GetType(mapping.Attribute("contract").Value),
             Type.GetType(mapping.Attribute("type").Value))).ToDictionary(
        kvp => kvp.Key, kvp => kvp.Value);

Die Instanziierung des konkreten Typs in der Methode GetService ist sogar noch wesentlich einfacher, und kann in einer einzigen Zeile erfolgen:

// Instantiate the type and return it to the caller.
return (TContract)Activator.CreateInstance(_mappings[typeof(TContract)]);

Um nun tatsächlich eine Instanz der Klasse MemoryStream an Hand ihres Contracts zu erzeugen, genügt eine Referenz auf den Microkernel und folgende Zeile:

// Try to get an instance for an IDisposable type.
IDisposable memoryStream = ServiceLocator.GetService<IDisposable>();

Dieser Microkernel kann nun bereits verwendet werden, um Komponenten innerhalb eines Projektes zu entkoppeln. Häufig ist der zu instanziierende Typ aber gar nicht Bestandteil des eigentlichen Projektes, sondern liegt in einer eigenständigen Assembly, die potenziell erst nach dem Kompilieren zu einer bereits erfolgten Installation beigefügt wird.

Alles, was zur Kompilierungszeit in einem solchen Fall zur Verfügung steht, ist der Contract – der eigentliche Typ wird unabhängig davon bereitgestellt, um beispielsweise Addins zu ermöglichen.

Um ein solches Szenario zu unterstützen, muss der Microkernel also eine Möglichkeit bieten, einen Typ auch aus einer Assembly zu laden, die neben dem eigentlichen Projekt losgelöst im Dateisystem liegt, ohne dass sie bereits zur Kompilierzeit verfügbar war.

Das schöne ist: Dieses Szenario wird bereits unterstützt. Alles, was hierfür notwendig ist, ist, in der Konfigurationsdatei neben der Klasse auch noch die enthaltende Assembly anzugeben, wie folgendes Beispiel an Hand der Post-Klasse von BlogEngine.NET zeigt:

<?xml version="1.0" encoding="utf-8" ?>
<serviceLocator>
  <mappings>
    <mapping contract="System.IDisposable" type="BlogEngine.Core.Post, BlogEngine.Core" />
  </mappings>
</serviceLocator>

Mehr ist für einen eigenen Microkernel prinzipiell nicht notwendig. Natürlich empfiehlt es sich, diesen für einen produktiven Einsatz noch an etlichen Stellen zu optimieren und um weitere Funktionen zu ergänzen. Die grundlegende Funktionsweise dürfte aber klar geworden sein.

Architektur lernen

Donnerstag, 27. November 2008, 13:34 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Von Zeit zu Zeit werde ich gefragt, wie man als Entwickler eigentlich dem Thema Architektur begegnen sollte und wie man einen entsprechenden Einstieg findet. Da es kein allgemein gültiges Rezept gibt, empfehle ich in der Regel die Beschäftigung mit typischen Architekturthemen, an Hand derer ein Einstieg gelingen kann. Dazu gehören unter anderem:

  • Die klassischen Entwurfsmuster. Auch wenn diese letztlich nur ein Werkzeug für den Architekten darstellen, verhilft die Beschäftigung mit ihnen zu einer anderen Denkweise, vor allem hinsichtlich des Nutzens von Schnittstellen. Entwurfsmuster helfen, die eigene objektorientierte Denkweise zu schulen und zu verbessern, was sich wiederum in besser strukturiertem Code niederschlägt. Ein äußerst empfehlenswertes Buch zu diesem Thema ist C# 3.0 Entwurfsmuster von Judith Bishop, das neben den eigentlichen Entwurfsmustern auch deren Einsatz in C# 3.0 beschreibt.
  • Der Einsatz eines Microkernels. Auch dieses Thema fördert das Denken in Schnittstellen, allerdings auf einer anderen Ebene als die Entwurfsmuster - beziehen diese sich nämlich auf das Klassendesign, bezieht sich der Einsatz eines Microkernels auf das Komponentendesign einer Anwendung. Zwar gibt es einige Projekte wie beispielsweise Unity, die man sich näher anschauen kann, allerdings ist ein einfacher Microkernel in wenigen Zeilen selbst implementiert, wobei der Lerneffekt dann allerdings weitaus größer ist.
  • Die aspektorientierte Programmierung. Als Werkzeug empfehle ich hierfür regelmäßig PostSharp mit dem dazugehörigen LAOS-Framework, um einen Einstieg in die Beschäftigung mit den sogenannten Crosscutting Concerns zu erhalten. Als typische Einsatzgebiete für den Anfang dienen in der Regel die Themen Validierung, Sicherheit, Logging und Transaktionen.
  • Der Einsatz von Softwarezellen. Diese sind ein Konzept von Ralf Westphal, mit dessen Hilfe sich die Architektur einer Anwendung auf jeder Granularitätsebene darstellen lässt, und das etliche der gängigen Nachteile des klassischen Schichtenmodells vermeidet. Nähere Informationen hierzu finden sich in Ralfs Blog unter dem Tag Software Cells.

Die meisten Entwickler fragen trotz dieser Fülle an Informationen nach zusätzlicher, empfehlenswerter Literatur. Eine wirklich befriedigende Antwort konnte ich auf diese Frage bislang jedoch nicht geben, da es sich bei Architektur - im Gegensatz zu beispielsweise einer Programmiersprache - um ein ausgesprochen weites Feld handelt, das nicht mit der Lektüre einiger Bücher abgedeckt ist.

Vielmehr erfordert eine gute Architektur vor allem viel Erfahrung, und diese erlangt man letztlich nur durch Learning by Doing. Trotzdem wäre es natürlich hilfreich, zumindest einen Leitfaden mit Fragen an der Hand zu haben, an dem man sich orientieren kann, und der einen in die richtige Richtung denken lässt.

Genau einen solchen Leitfaden hat das patterns & practices-Team von Microsoft nun unter dem Namen Application Architecture Guide bereits in einer zweiten Version veröffentlicht, der kostenlos von der entsprechenden Webseite heruntergeladen werden kann.

In fünf Abschnitten wird detailliert auf verschiedene Themen wie allgemeine Architekturüberlegungen, Dienste, die verschiedenen Schichten, Performance, Sicherheit, RIAs, Webanwendungen und zahlreiches anderes mehr eingegangen. Erfreulicherweise ist der Inhalt größtenteils nicht als Fließtext, sondern als eine Art Checklisten implementiert, was dem Lesefluss und vor allem der praktischen Anwendbarkeit des Guides sehr zu Gute kommt.

Von Entwurfsmustern und Werkzeugen

Freitag, 19. September 2008, 22:32 Uhr
Permalink | Kommentare (0) | Kommentare als RSSRSS

Der österreichische Philosoph und Autor Paul Watzlawick hat folgenden Satz formuliert:

"Wer als Werkzeug nur einen Hammer hat, sieht in jedem Problem einen Nagel."

Diese Aussage begründet in meinen Augen äußerst zutreffend, warum es für Architekten ausgesprochen wichtig ist, Entwurfsmuster zu kennen. Gregor Biswanger hat in seinem Kommentar zu meinem Blogeintrag Architektur = Planen + Entwurfsmuster angemerkt, dass ein gezieltes Denken in Entwurfsmustern mehr schaden als nutzen könne.

Und ich muss zugeben: Gregor hat damit vollkommen recht! Auch ich vertrete den Standpunkt, dass man nicht versuchen sollte, möglichst viele Entwurfsmuster auf Biegen und Brechen in eine Architektur zu stopfen, denn letztlich verfehlt man damit in der Regel sein Ziel und arbeitet daher kontraproduktiv.

Statt dessen sehe ich Entwurfsmuster als Werkzeuge, deren Kenntnis äußerst hilfreich sein kann, deren Einsatz aber auch bewusst und bedacht für jedes Muster individuell entschieden werden muss. Der Bedarf bestimmt also im Idealfall, welches Werkzeug benutzt wird, und nicht die eigene Fähigkeit, das Werkzeug tatsächlich benutzen zu können.

Im Prinzip verhält es sich mit Entwurfsmustern somit wie mit handwerklichen Werkzeugen, wie beispielsweise Hammer, Schraubenzieher und Säge: Um einen Nagel in der Wand zu befestigen, genügt ein Hammer, es ist nicht notwendig, den Schraubenzieher oder die Säge zu verwenden. Trotzdem ist es sinnvoll, auch diese Werkzeuge zu kennen, so dass man nicht versucht, auch eine Schraube mit dem Hammer in die Wand zu treiben.

Allgemein kann man also sagen, dass ein großes Wissen zwar von Nöten ist, um Probleme überhaupt erkennen zu können, dass aber Erfahrung und Weisheit dazugehören, sein Wissen zielgerichtet einsetzen zu können.

Architektur = Planen + Entwurfsmuster

Donnerstag, 11. September 2008, 11:25 Uhr
Permalink | Kommentare (4) | Kommentare als RSSRSS

Vor einigen Tagen habe ich versucht, die Frage Was ist Architektur?, die mir Peter Bucher einige Wochen zuvor gestellt hat, zu beantworten. Peter hat daraufhin meinen Eintrag direkt aufgegriffen und ihn in einem eigenen Eintrag um einen wesentlichen Aspekt ergänzt.

Ein guter Anfang um den Zielen "Qualitativ gute Software" und "Entwickler" Rechnung zu tragen, ist sicher die vorgängige Auseinandersetzung mit den Anforderungen. Also auch mal den Block zur Hand nehmen. Etwas auf Papier bringen, aus verschiedenen Blickwinkeln betrachten und dann erst einen Prototyp zu bauen.

Diesen Sätzen möchte ich mich ohne jegliche Einschränkung anschließen, meiner Meinung nach kann man den Aspekt der Planung für die Architektur nicht genug betonen, er ist sozusagen die Essenz des Ganzen.

Doch warum ist das so? Warum ist eine vernünftige Planung so wichtig für die Softwareentwicklung?

Die Antwort ist simpel: Es liegt daran, dass grundlegende Korrekturen desto teurer werden, je weiter fortgeschritten das Projekt ist. Im Prinzip ist es wie beim Hausbau:

  • Den Grundriss des Hauses auf dem Papier zu ändern, bevor mit dem Bau begonnen wurde, ist simpel.
  • Den Grundriss des Hauses zu ändern, sobald das Fundament gelegt wurde, ist möglich, aber mit Aufwand verbunden.
  • Den Grundriss des Hauses zu ändern, bevor abschließend das Dach aufgesetzt wird, ist extrem aufwändig und teuer.

Genauso verhält es sich bei der Softwareentwicklung: Je früher eine Änderung notwendig ist, desto weniger Code ist direkt (und indirekt!) betroffen und desto weniger langwierige Änderungen sind notwendig. Da jede Codeänderung zudem entsprechenden, eigentlich vermeidbaren, Testaufwand nach sich zieht, wird schnell klar, warum dies so ist.

Es rentiert sich also durchaus, eine Software zunächst grundlegend und detailliert zu durchdenken, bevor man sich an die Implementierung begibt. Besonders nützlich ist natürlich, wenn sich für gewisse Situationen bereits Lösungsansätze oder Vorgehensweisen bewährt haben, die man lediglich erneut einsetzen muss. Dann wird nämlich der Aufwand so wohl für die Planung wie auch für die Entwicklung geringer.

Daraus wird auch ersichtlich, warum Entwurfsmuster für die Softwarearchitektur eine so große Rolle spielen: Sie geben dem Architekten erprobte Werkzeuge an die Hand, um gewisse Aufgaben zu lösen. Für eine gute Architektur ist es zwar nicht ausreichend, Entwurfsmuster miteinander zu kombinieren, aber sie zur Hand zu haben und zu wissen, welches Entwurfsmuster man in welchem Kontext wie einsetzen kann, ist enorm hilfreich.

Außerdem erleichtern Entwurfsmuster die Kommunikation zwischen Architekten und Entwicklern, denn beide Seiten haben damit eine gemeinsame Terminologie, die exakt definiert wurde. So bleibt weniger Raum, in dem Missverständnisse entstehen können, und es lassen sich überflüssige Diskussionen vermeiden.

Häufig werde ich gefragt, wie man beginnen sollte, sich mit Architektur zu beschäftigen. Ich habe dafür zwar kein festes Schema, denn einen großen Teil macht schlichtweg die Erfahrung und die Beschäftigung mit der Materie aus, aber ich rate fast jedem Entwickler, der mich dies fragt, dazu, sich mit Entwurfsmustern auseinanderzusetzen und versuchen, zu verstehen, warum diese so und nicht anders aufgebaut sind.

Im Lauf der Zeit ergeben die einzelnen, zunächst isoliert erscheinenden Entwurfsmuster, dann ein Netz, das sich immer dichter verknüpfen lässt, und man beginnt, die Konzepte hinter den Entwurfsmustern zu verstehen. Ist dieser Punkt erst einmal erreicht, so kann man dies durchaus als einen gelungenen Start in die Welt der Architektur bezeichnen.

Was ist Architektur?

Mittwoch, 3. September 2008, 18:37 Uhr
Permalink | Kommentare (2) | Kommentare als RSSRSS

Von Zeit zu Zeit wird man in seinem Leben mit einer bestimmten Art von Fragen konfrontiert, bei denen jeder - vor allem man selbst - davon ausgeht, dass man diese mit Leichtigkeit beantworten können müsste, aber: Nichts da! Just in dem Moment, in dem man zu einer Antwort ansetzt, fällt einem ein Aspekt ein, den man noch nicht bedacht hat. Und je länger man über die Antwort nachdenkt, desto tiefer steigt man in die verworrenen Feinheiten der Frage ab und kommt nach Tagen zu dem Ergebnis, dass man eigentlich mit dem Nachdenken und all den neu gewonnenen Erkenntnissen noch einmal ganz von vorne anfangen müsste.

Vor einigen Wochen war es bei mir wieder einmal so weit: Peter Bucher, MVP für ASP.NET und ein von mir äußerst geschätzter MSN-Kontakt, hat mich gefragt, wie ich eigentlich den Begriff der Softwarearchitektur definieren würde. Als Architekt, so dachte ich, müsste mir die Antwort bereits vorformuliert auf der Zunge liegen, nur noch darauf wartend, dass ich sie ausspreche. Meine spontane Antwort lautete, dass ich erst einmal einen Tag Bedenkzeit benötigte.

Aus dem einen Tag sind inzwischen einige Wochen geworden, und ich glaube, die Frage inzwischen zumindest nach bestem Wissen und Gewissen als Diskussionsgrundlage beantworten zu können. Ich bin mir sicher, dass ich mindestens einen, garantiert aber eher zahlreiche Aspekte nicht abgedeckt habe, aber eben deshalb stellt meine Antwort auch nur eine Grundlage für eine weiterführende Diskussion dar.

Der Frage von Peter liegt meines Erachtens eine andere, tiefer gehende Frage zu Grunde: Wie schreibt man gute Software? Letztendlich geht es bei der Entwicklung von Software auch in Zeiten von C#, WPF und Silverlight immer noch um das klassische EVA-Prinzip: Irgendwelche Daten werden eingegeben, sie werden verarbeitet und schließlich wieder ausgegeben. Egal, ob es sich um eine kryptische Konsolenanwendung oder eine grafisch aufpolierte und moderne Web 2.0-Anwendung handelt, die in der neuesten Hype-Technologie geschrieben wurde: Fehlt einer dieser drei Aspekte, verliert die entsprechende Anwendung ihren Sinn.

Anders formuliert: Anwendungen sind dazu da, um Probleme zu lösen, wobei das Wort Problem im weitesten Sinne gebraucht wird (auch das Schreiben einer E-Mail an meine Schwiegereltern ist in diesem Zusammenhang ein Problem, obwohl ich sehr nette Schwiegereltern habe). Man könnte also auch sagen, Anwendungen sind dazu da, um Aufgaben zu meistern, die wir ihnen stellen, und die wir ohne sie (zumeist) nicht lösen könnten. Natürlich kann ich an Stelle einer E-Mail auch einen Brief auf einer Schreibmaschine schreiben, aber das hätte dann nichts mehr mit Software zu tun.

Wann ist also eine Software gut? Dann, wenn sie die zu lösende Aufgabe löst, wenn sie also ihre Funktion erfüllt, wegen derer sie überhaupt erst geschrieben wurde. Doch dies ist - wenn nicht falsch - so doch im äußersten Maße unzureichend. Denn die Bereitstellung der Funktionalität ist zwar eine notwendige Voraussetzung, sie ist aber bei weitem nicht die einzige: Neben der Funktionalität spielen zahlreiche Faktoren wie Stabilität, Portabilität, Erweiterbarkeit, Austauschbarkeit, Wartbarkeit, ... eine Rolle - und hier kommt schnell eine weitere Frage ins Spiel: Interessant ist auf einmal nämlich nicht mehr, was gemacht wird, sondern wie es gemacht wird.

Genau das ist der entscheidende Unterschied zwischen Programmierung, Entwicklung und Architektur: Während sich die Programmierung mit dem reinen Was auseinandersetzt, beschäftigt sich Architektur mit dem Wie. Und die Schnittstelle zwischen beidem - quasi die architekturbewusste Programmierung - ist die Entwicklung. Deshalb besteht für mich auch immer ein großer Unterschied zwischen Programmierern und Entwicklern - während erstere nur versuchen, ihren Code irgendwie herunterzuschreiben, denken zweitere auch darüber nach, wie man Code beispielsweise effizient, sicher oder performant gestaltet.

Nun ist es aber auch so, dass es nicht nur einen einzigen Weg gibt, der glücklichmachend ist - das Wie kann also auf viele verschiedene Arten umgesetzt werden. So könnte der Schwerpunkt der Entwicklung beispielsweise auf eine möglichst gute Erweiterbarkeit oder eine möglichst gute Portabilität gelegt werden. Idealerweise sollten alle diese Aspekte gleichermaßen berücksichtigen, aber das ist aus Zeit-, Geld- oder sonstigen Gründen oftmals nicht möglich. Häufig schließen sich bestimmte Aspekte sogar gegenseitig aus. Performance und Plattformunabhängigkeit beispielsweise: Entweder schreibt man performanten, an eine Plattform optimierten und damit gebundenen Code - oder generischen, plattformunabhängigen Code, der dafür nicht mehr das Optimum aus der jeweiligen Plattform herausholt.

Insgesamt ergeben sich daraus viele verschiedene Stile, wie man das Wie leben kann. Und letztendlich führen diese Überlegungen, die für jede Software neu angestellt werden müssen, letztlich zu einer bestimmten Art und Weise der Herangehensweise - eben der Architektur.

Zusammenfassend könnte man also sagen, dass Architektur das vorausschauende Planen einer Software unter Berücksichtigung aller derzeitig und potenziell zukünftig relevanten Aspekte in dem ihnen zustehenden Maß ist, ohne dabei die Funktionalität aus den Augen zu verlieren, auf Grund derer die Software überhaupt erst entwickelt wird.