DevTips.NET Logo

Je bent niet ingelogd. Login

Microsoft .NET Magazine voor developers

 
Gratis abonnement!

 

 
Gratis abonnement!
Gratis proberen!
Klik hier

MSDN Magazine


.NET Magazine

 
.NET Developer

Implementatie van 3-laags architectuur met ObjectDatasource

jouw beoordeling:

Waarom Drielaags Architectuur?

Door een applicatie te partitioneren volgens het 3-laags principe (presentatie-, logische- en datalaag) wordt deze veel beter onderhoudbaar, leesbaarder, schaalbaarder en zijn de onderlinge afhankelijkheden beheersbaar of in ieder geval tot een minimum beperkt waardoor de flexibiliteit toeneemt. Er is vaak verwarring tussen de begrippen 'tier' en 'layer' (beiden ruwweg te vertalen in 'laag'). Een 'tier' beschrijft een fysiek kenmerk, terwijl een 'layer' een logische scheiding impliceert. Bij een 3-tier applicatie onderscheiden we (zie figuur 1):


figuur 1: de lagen in een 3-tier applicatie.

  1. Een presentatie-tier waarin de informatie wordt weergegeven b.v. een webbrowser.
  2. Een logische tier waarin de informatie wordt verwerkt. Deze laag is meestal opgesplitst in twee aparte software lagen: de business laag en de data access laag.
  3. De data-tier waarin zich de opslag bevindt, meestal een relationele database. Hiermee bedoelen we dus de fysieke SQL server.

Indien de presentatie- en de logische tiers gefuseerd zijn dan spreekt men van een Client-Server model (strikt genomen een 2-tier model). Vanuit een software-ontwikkelingperspectief zien wij alleen maar de presentatielaag, de business en data access laag (de logische lagen).

  • De presentatielaag omvat enkel alleen UI code en event handlers waarbij in de code alleen maar business objecten en UI controls manipuleert.
  • De businesslaag is het 'hart' van de applicatie, ook wel het 'model' genoemd. Men modelleert hier het proces wat men wil automatiseren, inclusief alle business logica (business rules) in objecten.
  • De data accesslaag bevat code voor de persistence van de business laag. In de praktijk betekent dit echter dat we de objecten vertalen naar tabellen en kolommen en opslaan in een SQL database. Opslag kan echter ook in een object database plaatsvinden of d.m.v. het aanroepen van webservices. Belangrijk is dat de data laag alleen maar business laag objecten teruggeeft en geen objecten waarin kennis van de structuur van de database nodig is, zoals bij DataSets.

Let er dus op dat de presentatie, business- en data accesslaag vanuit een architectureel perspectief zich fysiek op dezelfde plaats (namelijk in de logische tier) bevinden. Tevens praten lagen alleen met naastgelegen lagen, dus een presentatielaag zal nooit rechtstreeks een datalaag aanroep doen, maar alleen de businesslaag.

In de praktijk wordt een 3-laags ontwerp vaak alleen maar met de mond bedreven en beperkt geïmplementeerd omdat dit met .NET 1.1 niet goed ondersteund werd. Voorheen was het alleen mogelijk om een data-bound control rechtstreeks aan een database te koppelen (b.v. via SqlDatasource) en veel ontwikkelaars nemen daardoor de makkelijke route en koppelen hun databound controls rechtstreeks aan een DataSet die men zelf opgehaald heeft of aan een datasource welke DataSets teruggeeft waardoor er een afhankelijkheid ontstaat naar de naamgeving van de DataGrid kolommen in de database tabellen, iets wat we willen vermijden.

Met .NET 1.1 kunnen we dit omzeilen door of alleen business-objecten terug te geven vanuit de datalaag. Maar omdat koppelen van business-objects met databound controls niet rechtstreeks ondersteund wordt betekent dat men extra code moet schrijven om het object weer te geven in b.v. een DataGrid door de data er 'handmatig' in te stoppen of door bepaalde interfaces te implementeren op het business object (IList, IListSource). Slechts weinigen realiseren zich dat men in de Datasource property van een databound control ook een Array, ArrayList of generic List kan gebruiken en dat de kolomnamen in de wizards net zo goed properties van objecten kunnen zijn omdat alle objecten die men in een databound control stopt uitgevraagd worden door middel van .NET reflectie. Let wel dat er verder geen support is vanuit Visual Studio en dat we dus zelf controls moeten neerzetten welke in hun event handlers weer business-object-methoden aanroepen.


figuur 2: een eenvoudige implementatie van een 3-laags systeem.

ObjectDataSource

Maar vanaf nu wordt dit allemaal veel makkelijker omdat we vanaf .NET 2.0 beschikking hebben over zogenaamde ObjectDatasources. Wat zijn dit? Simpel gezegd stellen ObjectDatasources ons in staat om onze eigen business laag op een declaratieve manier te koppelen aan databound controls, gebruikmakend van Wizards. Er zijn dus nu geen excuses meer om geen 3-laags architectuur te implementeren!

Het mooie van ObjectDatasources is dat we niet meer in termen van kolommen en rijen hoeven te denken maar gewoon in objecten, een ObjectDatasource gebruikt namelijk reflectie om de properties te binden aan de kolommen van een GridView. Helaas voor ons heeft Microsoft ObjectDatasource alleen geïmplementeerd  in ASP.NET 2.0 zodat .NET 2.0 WinForm applicaties BindingSources moeten gebruiken (zie voorbeeld solution 2). We zullen daarom eerst laten zien hoe we een 3-laags WinForms applicatie onder .NET 2.0  kunnen maken en daarna hoe dit veel eenvoudiger gaat bij een webapplicatie met ObjectDatasource in ASP.NET 2.0.

Drielaags Architectuur in een .NET 2.0 WinForms applicatie

Voor WinForms applicaties kunnen we geen ObjectDatasource gebruiken als we een drielaags architectuur willen opzetten omdat dit niet ondersteund wordt. We kunnen wel gebruik maken van een BindingSource om hetzelfde te bereiken. Een BindingSource is een abstractielaag tussen een datasource en een visueel control. Omdat de datasource ook een object kan zijn en er Wizards zijn die BindingSources ondersteunen wordt ons werk hierdoor ook eenvoudiger. In de volgenlisting zien we de BL_Customer welke aangeroepen wordt door de presentatielaag en welke zelf weer de data accesslaag aanroept.

public class BL_Customer
    {
        public const int DEFAULT_CUSTOMER_ID = -1;
        int m_nId = DEFAULT_CUSTOMER_ID;
        string m_strFirstName;
        string m_strLastName;
        public void Customer(int nId, string strFirstName, string strLastName)
        {
            m_nId = nId;
            m_strFirstName = strFirstName;
            m_strLastName = strLastName;
        }
        public string FirstName
        {
            get { return m_strFirstName; }
            set { m_strFirstName = value; }
        }
        public string LastName
        {
            get { return m_strLastName; }
            set { m_strLastName = value; }
        }
        public int Id
        {
            get { return m_nId; }
            set { m_nId = value; }
        }
        public string DisplayName
        {
            get { return m_strFirstName + " " + m_strLastName; }
        }
	static public List GetAllItems()
        {
            return DL_Customer.GetAllItems();
        }
        static public List GetAllItemsFiltered(string strFirstName, string strLastName)
        {
            return DL_Customer.GetAllItemsFiltered( strFirstName, strLastName );
        }
        static public BL_Customer GetItemById(int nId)
        {
            return DL_Customer.GetItemById( nId );
        }
        static public int Insert(BL_Customer cust)
        {
            return DL_Customer.Insert( cust );         // new Id is returned by Datalayer
        }
        static public void Update(BL_Customer cust)
        {
            DL_Customer.Update( cust );         

        }
        static public void Delete(BL_Customer cust)
        {
            DL_Customer.Delete( cust );                 // only Id is used
        }
    }

We hebben een DL_Customer, een BL_Customer en een Form voor de presentatie laag. De DL_Customer is een statische klasse met alleen statische methodes terwijl een BL_Customer business-object zowel statische als klasse methoden heeft. Een business-object moet op zichzelf staan en methoden hebben die werken op de interne state maar bijvoorbeeld voor het ophalen van meerdere business objecten gebruiken we een statische methode GetAllItemsFiltered(). We hebben er hier niet voor gekozen om bijvoorbeeld een klasse methode Save(), Delete() of IsDirty() te implementeren, maar gebruiken statische methoden Update(), Delete() en Insert(). Ook doen we niet aan caching of filtering in de businesslaag om het voorbeeld eenvoudig te houden.

De datalaag DL_Customer bevat de database aanroepen en verwerkt de terugkomende database rijen naar businesslaag-objecten, welke in een generic list worden gestopt en terug worden gegeven aan de businesslaag. De datalaag krijgt in methoden Insert(), Update() en Delete() als parameter ook businesslaag-objecten mee. De methode DL_Customer.Insert() geeft een ID terug van het object en DL_Customer.Delete() verwacht dit ID ingevuld in het parameter object. We kunnen de implementatie van de datalaag vervangen van de bestaande SQL-instructies door stored procedures, een andere SQL database, object database of zelfs een webservice. Als er velden in de database van naam veranderen (wat nogal eens gebeurt) dan hoeven we dit alleen hier aan te passen.

We kunnen in het Form geen optimaal gebruik maken van de mogelijkheden die .NET ons geeft omdat we geen echt datasource-control gebruiken en we moeten dus zelf New, Save en Delete knoppen toevoegen welke rechtstreeks de methoden in de businesslaag aanroepen.

Als je de code bij dit artikel uitvoert (SQL Server Express dient geïnstalleerd te zijn) dan zijn de normale insert, update en delete operaties mogelijk op het form. Tevens kunnen we zoeken naar een bepaalde voor of achternaam. Er is echter enig werk nodig om dit allemaal voor elkaar te krijgen en we kunnen niet van alle handige Wizards in Visual Studio gebruik maken.

Drielaags Architectuur in ASP.NET 2.0 met ObjectDatasource

In Visual Studio 2005 en hoger is er een goede integratie van de ObjectDatasource met de data-bound controls van ASP.NET. Met een Wizard is het een fluitje van een cent om een web-formulier te maken waarmee we een tabel onderhouden uit een SQL database waarbij we een drielaags architectuur als basis verkrijgen. Uitgangspunten zijn weer dezelfde datalaag en business-objecten DL_Customer en BL_Customer als in ons .NET 1.1 voorbeeld. We hoeven dit keer geen code te schrijven om de business laag te koppelen aan de presentatielaag omdat de visuele controls als datasource de ObjectDatasource gebruiken die weer gekoppeld is aan de Business laag.

In het voorbeeld koppelen we een GridView en een DetailsView aan twee ObjectDatasources, welke beide gekoppeld zijn aan ons BL_Customer business object. Bij het configureren van de ObjectDatasource krijgen we een lijst te zien met objecten in ons project (zorg ervoor dat deze zichtbaar zijn door de classes in App_Code te stoppen, de classes public te maken en de relevante methoden van deze classes ook). Daarna geven we aan welke methoden van het business object we gaan gebruiken voor ophalen, toevoegen, wijzigen en verwijderen. Door interactie tussen de visuele controls en de ObjectDatasource kunnen we nu zonder code te schrijven al de GridView laten vullen. In ons voorbeeld willen we ook kunnen updaten en daar gebruiken we een DetailsView voor. De code die we geschreven hebben is feitelijk alleen maar om de user interface aan te passen aan de modus van de applicatie. De kern van onze applicatie zit nog steeds in de business objecten, waar het ook hoort.

Ons eerdere voorbeeld geeft ons een goede indruk hoe een drielaags-architectuur op te zetten maar in de praktijk is de businesslaag veel complexer, met meerdere afhankelijke business-objecten. Hoe gaan we hiermee om? Een customer object zou bijvoorbeeld een veld met het land waar de klant zich bevindt in zich kunnen herbergen. In object-geörienteerde terminologie zou dit dus een has-a relationship zijn, maar als we een SQL datalaag hebben dan is het efficiënter om een join te doen van de Customer en de Countries tabel en niet twee aparte query's te doen naar beide tabellen. Voor lookup tabellen zoals dit dienen we pragmatisch te werk te gaan en is het het beste om het land veld gewoon deel te laten uitmaken van het Customer object. Dus we krijgen dus een Customer object welke een 'Country' veld bevat als string welke opgehaald werd door de join van de tabellen. Er zou dan ook een Countries business laag object zijn welke alleen bedoeld is voor CRUD manipulaties van de Countries tabel (uitgaande dat we een SQL backend gebruiken, wat helemaal niet het geval hoeft te zijn).

Bij het klassieke order-orderdetail voorbeeld zijn de detailregels wel aparte objecten omdat we deze apart willen kunnen muteren. Ons Order-object zou dus een generic list bevatten van OrderLines objecten. Afhankelijk van de wensen van de klant qua user interface zouden we een methode GetOrders() kunnen definiëren die wel voor elke order ook meteen de OrderLines erbij zoekt en een GetOrdersWithoutDetailLines() die dat niet doet. Een GetOrderById() methode zou waarschijnlijk wel altijd de Detail Lines meteen ophalen en hiervoor twee aparte query's (Order en OrderLines tabellen) uitvoeren.

Geavanceerde Technieken

In .NET wordt veel gebruik gemaakt van het Provider-model en we kunnen dat bij onze 3-laags architectuur ook doen voor de datalaag zodat we bijvoorbeeld onze SQL Server data-access Provider kunnen vervangen door een Web Services Provider.

In de businesslaag kunnen we caching toepassen om de performance en schaalbaarheid nog verder te vergroten. Bij lezen checken we of het item al in de cache staat en zo niet dan lezen vragen we het object op uit de datalaag en stoppen deze in de cache. Bij wissen, updaten of toevoegen van een object wordt de cache geleegd zodat de eerstvolgende opvraag weer resulteert in een datalaag aanroep.

Paging kan ook ondersteund worden door de Business Layer GetXXX() methode twee extra parameters mee te geven, StartIndex en MaximumRows en de ROW_NUMBER() T-SQL functie te gebruiken in onze stored procedure.

Altijd Gebruiken

Mijn drijfveer om dit artikel te schrijven is mijn persoonlijke ervaring dat op veel ontwikkelprojecten waar ik er later bijgeschakeld werd men geen drielaags-architectuur gebruikte. Hierdoor kostte kleine wijzigingen relatief grote inspanningen. Erger was dat als men eenmaal de verkeerde architectuur had gekozen dat omschrijven te kostbaar was en men dus genoodzaakt was om ermee verder te gaan.

We hebben gezien dat 3-laags architectuur niet eens zo erg moeilijk is om op te zetten en dat  vanaf ASP.NET 2.0 Microsoft uitstekende tools heeft gemaakt om 3-laags architectuur met vrijwel hetzelfde gemak als op DataSet gebaseerde code te creëren. Daarom dient 3-laags architectuur tot het standaard gereedschap te behoren van elke .NET ontwikkelaar.

De code voor dit artikel is hier te downloaden.

Over de auteur: Olaf Beckman

Overige links

Gepubliceerd op zondag 21 december 2008 door Olaf Beckman


Commentaar



Reageer

Naam:

Jouw url (optioneel):

Commentaar:

HIP Voer de code in: