16 Magazin AdventureWorks Cinema, Teil 4 Die Architektur des AdventureWorks Cinema Server, Teil 4 von Immo Landwerth und Mathias Raacke Server Design auf Basis von Web Services und SQL Server. Der vierte Teil der Artikelreihe über das AdventureWork Cinema Sample stellt die Geschäftslogik und den Datenbankzugriff vor. Wie in der ersten Folge dieser Reihe beschrieben [1], ist AdventureWorks Cinema eine verteilte Lösung, deren Funktionalität in Schichten realisiert ist. Der Server selbst wurde ebenfalls in Schichten aufgeteilt, was es ermöglicht, die Verantwortlichkeiten gezielt zu kapseln und so insgesamt die Wartbarkeit zu verbessern. Abbildung 1 stellt den Server und die einzelnen Teile bezogen auf die Gesamtarchitektur dar. Nachdem in der letzten Ausgabe [4] die Designentscheidungen, der Webclient und der Web Service vorgestellt wurden, widmet sich dieser Artikel der Geschäftslogik, dem Datenbankzugriff und der Datenbank. Geschäftslogik de zur Definition eigener Zugriffsregeln in der Applikation ist die rollenbasierte Sicherheit. Jeder Benutzer der Applikation ist dabei Mitglied bei einer oder mehreren Rollen. Die Rechte eines Benutzers werden hier direkt aus der Menge seiner Rollen abgeleitet. Ob dieser Benutzer eine Operation ausführen darf, hängt damit lediglich von seinen Rollenzugehörigkeiten ab. Die Überprüfung kann im Code auf verschiedene Arten erfolgen: Das .NET Framework stellt z.B. die Klasse PrincipalPermission zur Verfügung, mit der man die Mitgliedschaft durch den Aufruf der Demand-Methode fordern kann. Ist der Benutzer kein Mitglied, löst der Aufruf eine SecurityException aus. Es gibt auch die Möglichkeit, die Prüfung rein deklarativ Bei allen Anwendungen bildet die Geschäftslogik auf dem Server das eigentliche Herz der Applikation – naja, bei fast allen. Da die Geschäftslogik ohnehin in jeder Anwendung anders aussieht und um AdventureWorks Cinema nachvollziehbar zu halten, wurde auf eine allzu komplexe Geschäftslogik verzichtet. Stattdessen haben sich die Entwickler auf ein häufig anzutreffendes Thema konzentriert: Die Implementierung anwendungsspezifischer Sicherheitsmechanismen. In jeder Anwendung muss schließlich einmal ein Machtwort gesprochen werden – es soll ja nicht jeder machen können, was er will. Eine leistungsfähige (und vom .NET Framework direkt unterstützte) Metho- Abb. 1: Der Server in der Gesamtarchitektur 6.06 mit der Klasse PrincipalPermissionAttribute zu erledigen. Dieses Attribut wird einfach vor die zu schützende Methode geschrieben, womit die CLR bereits beim Aufruf sicherstellt, dass der Benutzer Mitglied bei der entsprechenden Rolle ist – ist das nicht der Fall, löst die CLR wiederum eine SecurityException aus. Falls mehrere solcher Attribute vor der Methode stehen, reicht es, wenn der Benutzer Mitglied in einer dieser Rollen ist, d.h. mehrere Attribute werden mit ODER und nicht mit UND verknüpft. Aufgrund der einfacheren Schreibweise haben sich die Entwickler bei AdventureWorks Cinema für die attributierte Variante entschieden. Listing 1 zeigt anhand eines Auszuges der Klasse MovieManager, wie die Prüfung in der Geschäftslogik von AdventureWorks Cinema gelöst wurde. Stellt sich nur noch die Frage, woher das Framework eigentlich weiß, wer der aktuelle Benutzer ist und in welchen Rollen er Mitglied ist. Die Antwort auf diese Fragen liefert die statische Eigenschaft CurrentPrincipal der Klasse Thread. Principal ist ein Objekt, das die Schnittstelle IPrincipal implementiert. Ein Principal repräsentiert einen angemeldeten Benutzer und seine zugehörigen Rollen. Das Framework assoziiert den Principal immer mit dem gerade aktiven Thread – das bedeutet, dass verschiedene Threads verschiedene Sicherheitskontexte haben. Insbesondere für Webapplikationen (und auch Web Services) ist das sehr wichtig. Der Benutzer selbst ist wiederum über die Schnittstelle IIdentity repräsentiert, welche im Wesentlichen nur seinen Namen und den Anmeldestatus beinhaltet. www.dotnet-magazin.de Anzeige 18 Magazin AdventureWorks Cinema, Teil 4 tierungen in AdventureWorks Cinema. Nachdem eine Instanz einer IPrincipal implementierenden Klasse erzeugt wurde, braucht diese nur einfach an Thread .CurrentPrincipal zugewiesen werden und – voilà! – rollenbasierte Sicherheit steht zu Diensten. Datenbankzugriff Abb. 2: IPrincipal und IIdentity in AdventureWorks Cinema Rollen werden einfach als Strings repräsentiert. Das Framework liefert eine ganze Reihe von vorgefertigten Implementierungen für IIdentity und IPrincipal mit, zum Beispiel WindowsIdentity und WindowsPrincipal, mit denen man die Listing 1 Verwendung der Klasse PrincipalPermissionAttribute public static class MovieManager { public static MovieInfo[] GetMovies() { // Diese Methode kann von allen Benutzern (auch // nicht angemeldeten) aufgerufen werden. } Windows-Rollen des angemeldeten Benutzers (oder eines anderen impersonifizierten Benutzers) als Rechtegrundlage verwenden kann. Im Allgemeinen ist man jedoch völlig frei in der Wahl der Rollen und kann selbst entscheiden, wann und woher diese geladen werden. In AdventureWorks Cinema stehen die Rollen beispielsweise in der Datenbank und werden direkt nach erfolgreicher Anmeldung über die Log-On-Methode des Web Services geladen. Abbildung 2 zeigt die Schnittstellen und die korrespondierenden Implemen- Aufgabe der Datenzugriffsschicht (Data Access Layer – DAL) ist, Geschäftsobjekte in die Datenbank zu schreiben beziehungsweise aus ihr zu extrahieren. Um den Datenbankzugriff von der konkreten Datenbank zu abstrahieren, gibt es grundsätzlich zwei Lösungsansätze. Der eine besteht darin, in der Implementierung des DAL nicht gegen Klassen von konkreten Datenbank-Providern (wie z.B. SqlCommand) zu programmieren, sondern gegen die allgemeinen Interfaces bzw. abstrakten Basisklassen (IDbCommand bzw. DbCommand). Allerdings ist damit nur der Kommunikationskanal unabhängig von der Datenbank – die SQL-Befehle sind es noch längst nicht. Bei diesem Ansatz muss man zusätzlich auf die verwendeten SQL-Befehle achten, damit keine proprietären Erweiterungen (z.B. PL/SQL bei Oracle oder T-SQL bei SQL Server) verwendet werden. Der kleinste gemeinsame Nenner aller zu unterstützenden Datenbanken ist in diesem Fall SQL. Damit beraubt man sich allerdings vieler interessanter Möglichkeiten der entsprechenden Listing 2 Auszug aus der Klasse CountryDal [PrincipalPermission(SecurityAction.Demand, Role = CinemaPrincipal.EmployeeRoleName)] public static class CountryDal // Validation des Parameters. { if(country == null) throw ExceptionBuilder.ArgumentNullException private const string CreateCountryProcedure = public static void CreateMovie(Movie movie) { // Diese Methode kann nur von Mitgliedern der Rolle “CreateCountry“; if(country.Id == Guid.Empty) public static void CreateCountry(Country country) // “Employee“ aufgerufen werden. throw ExceptionBuilder.ArgumentException { } (“country“, Resources.EmptyIdIsNotAllowed); // Aufbau eines Wörterbuchs mit den Parametern, die // an die gespeicherte Prozedur übergeben werden [PrincipalPermission(SecurityAction.Demand, // Parametern. [PrincipalPermission(SecurityAction.Demand, IDictionary<string, object> parameters = Role = CinemaPrincipal.EmployeeRoleName)] public static void RateMovie(User user, Guid movieId, // Diese Methode kann sowohl von Mitgliedern der // Rolle “Employee” als auch von “Customer” //aufgerufen werden. } 6.06 new Dictionary<string,object>(); parameters.Add(“@id“, country.Id); GetParameters(country); parameters.Add(“@name“, country.Name); (CreateCountryProcedure,parameters); // Rückgabe des erzeugten Wörterbuchs mit den } // Parametern. return parameters; private static IDictionary<string, object> GetParameters( Country country) { } IDictionary<string, object> parameters = SqlHelper.ExecuteStoredProcedure int value) { // Erzeuge ein Wörterbuch und füge alle Parameter hinzu. // sollen. Aufruf der Erstellungsprozedur mit den Role = CinemaPrincipal.CustomerRoleName)] (“country“); } } www.dotnet-magazin.de Magazin AdventureWorks Cinema, Teil 4 Datenbanksysteme (vor allem Stored Procedures und Trigger) – daher empfahl sich der zweite Ansatz. Bei diesem Ansatz wird analog zum Provider-Modell des SAL eine Schnittstelle auf Basis der Geschäftsobjekte und der notwendigen Anfragen (z.B. FindMovieByName) definiert. Die Implementierung kann für jedes Datenbanksystem spezifisch erfolgen, sodass als Datenbank auch Access unterstützt werden könnte, ohne dass zum Beispiel die SQL-Server-Implementierung auf Stored Procedures verzichten muss. Natürlich steht es den Entwicklern frei, zusätzlich auch einen einfachen Provider mit Standard-SQL und unter Verwendung von DbCommand und DbConnection zu entwickeln. Dieser bietet dann vielleicht nicht die optimale Performance, erlaubt dafür aber die Verwendung auf einer Fülle von Datenbankplattformen. Der DAL im Cinema Server wurde zwar nach dem zweiten Ansatz implementiert, allerdings wurde der Einfachheit halber nicht das Provider-Modell wie bei der Service-Zugriffsschicht (Ser- Abb. 3: Einsatzszenarien des SAL Anzeige 19 20 Magazin AdventureWorks Cinema, Teil 4 vice Access Layer – SAL [2] ) verwendet. Stattdessen wurde der DAL in statische Klassen implementiert. Die Funktionalität wurde nach Entität gruppiert und insgesamt über 15 Klassen verteilt. Es existiert beispielsweise eine Klasse MovieDal, die Filme lesen, schreiben, löschen und auch aktualisieren kann. Vorführungen werden analog über den PerformanceDal verwaltet. Aufgrund der Implementierung über statische Klassen ist der DAL momentan zur Laufzeit nicht austauschbar – das Provider-Modell analog zum SAL ließe sich aber leicht nachrüsten. Listing 3 Aktualisieren von Genres und Personen eines Films ALTER PROCEDURE dbo.UpdateMovieGenresAndPersons @movieId uniqueidentifier, @genresXml ntext, @personsXml ntext AS -- Remove all existing correlations DELETE FROM Movies_Genres WHERE Movie = @movieId DELETE FROM Movies_Persons WHERE Movie = @movieId -- Prepare Genres XML DECLARE @idoc int EXEC sp_xml_preparedocument @idoc OUTPUT, @genresXml -- Insert values from XML string into table Movies_Genres INSERT INTO Movies_Genres (ID, Movie, Genre) SELECT NEWID(), @movieId, ID FROM OPENXML(@idoc, ‘/genres/genre‘, 1) WITH (ID uniqueidentifier) -- Remove Genres XML EXEC sp_xml_removedocument @idoc -- Prepare Persons XML EXEC sp_xml_preparedocument @idoc OUTPUT, @personsXml -- Insert values from XML string into table Movies_Persons INSERT INTO Movies_Persons (ID, Movie, Person, PersonType) SELECT NEWID(), @movieId, ID, PersonType FROM OPENXML(@idoc, ‘/persons/person‘, 1) WITH (ID uniqueidentifier, PersonType int) -- Remove Persons XML EXEC sp_xml_removedocument @idoc 6.06 Die Datenbank Die Diskussion über das Datenbankschema gleicht in vielen Punkten den üblichen „Religionskriegen“ in der IT-Branche. Für die OOP-Puristen ist relationales .NET 2.0 unterstützt Sie besser denn je Mapping vollständig überflüssig. Die Datenbank ist lediglich ein gefälliger Datenspeicher, in dem am besten alle Geschäftsobjekte in einem riesigen Blob gespeichert werden. Zur Not darf es auch ein normalisiertes Schema sein, aber Stored Procedures sind selbstverständlich tabu („Geschäftslogik gehört doch nicht in die DB“). Die SQL-Fundamentalisten dagegen bevorzugen ein bis in alle Ewigkeit normalisiertes Schema, bei dem Niemanden der direkte Zugriff auf die Tabellen gestattet wird, sondern die Daten lediglich indirekt über Views und Stored Procedures gelesen und verändert werden können. Die Geschäftslogik wird dabei selbstverständlich vollständig in den Stored Procedures implementiert. Beide Ansätze haben ihre Vorteile, aber auch klare Nachteile. Neben der Fähigkeit, Detailwissen der Datenspeicherung zu kapseln, bieten Stored Procedures noch weitere Vorteile, beispielsweise können sie die Performance erhöhen und erlauben eine granularere Rechtekontrolle. Andererseits kann ein vollständig auf Stored Procedures basiertes Lesen in der Praxis sehr aufwändig werden. Eine vollständige Implementierung der Geschäftslogik in Stored Procedures ist allerdings fraglich, da beim Unterstützen mehrerer Datenbanken diese Logik automatisch über die Einzelimplementierungen der DALs verstreut werden muss. In AdventureWorks Cinema haben sich die Entwickler deshalb beim Schreiben für die Verwendung von Stored Procedures entschieden – beim Lesen wird allerdings direkt auf die Tabellen zugegriffen. Innerhalb der Stored Procedures werden keine Geschäftsregeln mehr geprüft, sondern es wird lediglich das direkte Schreiben der Daten implementiert. Stored Procedures werden hauptsächlich zur Kapselung von Detailwissen verwen- det, zum Beispiel können durch die Verwendung von XML mehrere Werte auf einmal übergeben werden. Das erlaubt, Interselektionstabellen „in einem Rutsch“ zu befüllen. Zusammenfassung Solange es den Menübefehl Datei | Neue Serveranwendung in Visual Studio noch nicht gibt, muss man als Entwickler schon selbst die grauen Zellen bemühen. Die Entwicklung einer verteilten Lösung ist allerdings keine triviale Aufgabe. Viele Dinge müssen bereits im Vorfeld berücksichtigt werden, damit es dann hinterher kein böses Erwachen gibt. Neben den grundlegenden Überlegungen zum Serverdesign haben die letzten beiden Artikel dieser Serie Ihnen auch Lösungswege für die kleinen Unwegsamkeiten aufgezeigt. Trotz der einen oder anderen Hürde und der hohen Komplexität gibt es jedoch noch eine gute Nachricht: Visual Studio 2005 und das .NET Framework 2.0 unterstützen Sie besser als jemals zuvor bei der Entwicklung verteilter Lösungen. Immo Landwerth ([email protected]) studiert Informatik an der Technischen Universität München, wo er auch als Microsoft Student Partner regelmäßig technische Vorträge hält. Darüber hinaus arbeitet er als Software-Entwickler für die The Project Group Informationstechnologie GmbH (www.theprojectgroup.de) in München. Mathias Raacke ([email protected]) arbeitet als Senior Student Partner bei der Microsoft Deutschland GmbH und studiert Informatik an der Universität Paderborn. Er entwickelt seit über drei Jahren Anwendungen mit .NET, leitet die .NET User Group Paderborn und betreut in der INETA studentische .NET User Groups. Immo und Mathias waren im AdventureWorks-Cinema-Projekt an der Entwicklung des Web Clients und des Backends beteiligt. Links & Literatur [1] Manuela Miller, Thomas Dallmair: AdventureWorks Cinema – besser als Kino, in: dot.net magazin 3.06 [2] msdn.microsoft.com/library/default.asp?url=/ library/en-us/dnpag/html/offline-CH01.asp [3] Download und Dokumentation aller Samples auf der MSDN Online Library: www.microsoft .com/germany/msdn/library/vs2005/samples [4] .NET-Sicherheit: www.microsoft.com/ germany/msdn/library/security/ ErhoehenDerSicherheitVonWebanwendungen/ secmod79.mspx [4] Immo Landwerth, Mathias Raacke: Architektur des AdventureWorks Cinema Server Teil 3, in: dot.net magazin 4.06 www.dotnet-magazin.de