AdventureWorks Cinema Teil 4

Werbung
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
Herunterladen