środa, 30 czerwca 2010

Architektura trójwarstwowa w ASP.NET

Architektura trójwarstwowa (three-tier architecture) pozwala na rozdzielenie systemu informatycznego na składowe zwane warstwami (tiers). Co zyskujemy dzięki takiemu podziałowi? Przede wszystkim łatwość wymiany modułów, bez potrzeby aktualizowania pozostałych.

Aplikacja trójwarstwowa składa się z następujących warstw:
  •  warstwy prezentacji - jest to najwyższa z warstw w tym modelu i to z nią prowadzi interakcję użytkownik aplikacji. Warstwa ta posiada najczęściej walidację wprowadzanych przez użytkownika danych oraz referencję do warstwy biznesowej. Nie wie natomiast nic na temat zasad działania systemu, wykorzystywanej bazy danych itp.
  • warstwa logiki biznesowej - warstwa ta przetwarza zapytania użytkownika. Można sobie ją wyobrazić jako "mózg" całej aplikacji. Warstwa ta jest "pośrednikiem" między warstwą prezentacji a warstwą dostępu do danych. Warstwa ta nie wie w jaki sposób "wydobywać" informacje z bazy danych.
  • warstwa danych - jest to jedyna warstwa aplikacji, która wie w jaki sposób pobierać, zapisywać i uaktualniać informacje w bazie danych. Warstwa ta nie wie kto pobiera informacje lub komu je przekazuje.
Warstwy można przedstawić w postaci diagramu:


Jak widać, między warstwami nie ma zależności - tak więc można dowolnie modyfikować jedną bez zmian w innych. Strzałkami zaznaczono kolejność przejścia zapytania od jednej warstwy do pozostałych. Czerwonymi strzałkami zaznaczono drogę zapytania od klienta do danych, a zieloną zwrócone dane.

Microsoft udostępnia wiele sposobów uzyskania takiej architektury. Najprostszymi z nich są LINQ oraz Entity Framework. Za pomocą tych narzędzi jesteśmy w stanie w prosty i szybki sposób wygenerować automatycznie schematy tabel i metod zawartych w bazie danych do postaci obiektów i metod pracujących na nich.

Starszym rozwiązaniem, lecz nadal stosowanym jest korzystanie z DataSet-ów. Postaram się przedstawić w jaki sposób korzystać właśnie z tego rozwiązania w budowaniu własnej aplikacji wielowarstwowej.

Na potrzeby omawianego zagadnienia stworzymy prostą bazę danych:



Do tabel wprowadzamy po kilka rekordów.

Teraz przejdziemy do tworzenia naszych warstw. Zaczniemy od najniższej - odpowiedzialnej za interakcję z bazą danych. Tak więc na początek tworzymy pustą solucję (Blank Solution) w której będziemy umieszczać kolejne komponenty.

Do naszej solucji na początek dodajemy projekt Class Library o przykładowej nazwie DataAccessLayer. W stworzonym projekcie tworzy się nam plik Class1.cs i dodawane są potrzebne referencje. Dla nas plik Class1.cs jest zbędny - usuwamy go więc. Następnie dodajmy do projektu DataSet i korzystając z Server Explorer "przeciągamy ze stworzonej przez nas bazy danych tabele do projektu:


Jak można zauważyć, referencja tabeli person i car z MS SQL została odwzorowana bez żadnych dodatkowych zabiegów z naszej strony.

Jak można zobaczyć w pliku CarsSet.Designer.cs, zostały stworzone klasy odpowiedzialne za tabele Car i Person, oraz klasy xTableAdapter, które pozwalają na łatwe manipulowanie danymi. Domyślnie otrzymujemy metody SELECT, UPDATE, INSERT, DELETE. Warto jednak przyjrzeć się im i dokonać modyfikacji. Dla przykładu biorąc komendę DELETE dla tabeli Car, zapytanie do bazy danych ma postać:

DELETE FROM Car
WHERE (IdCar = @Original_IdCar) AND (Marque = @Original_Marque) AND (Model = @Original_Model) AND (ProductionYear = @Original_ProductionYear) AND
(IdPerson = @Original_IdPerson)

Raczej nigdy nie skorzystamy z takiej postaci zapytania a w większości przypadków będziemy korzystali z usuwania po kluczu głównym - czyli IdCar. Tak więc modyfikujemy zapytanie do postaci:

DELETE FROM Car
WHERE (IdCar = @Original_IdCar)

Przejdźmy teraz do dodawania własnych zapytań. Klikamy prawym klawiszem myszy na DataSecie i wybieramy operację Add -> Query. Otwiera się edytor pozwalający na dowolną operację na danych w bazie. W moim przypadku chcę dodać zapytanie zwracające wyniki. Tak więc wybieram Use SQL statements a następnie SELECT which returns a single value (zapytanie zwracające pojedyncza wartość). Domyślnie otrzymujemy zapytanie:

SELECT COUNT(*) FROM Car

czyli zliczające ilość rekordów w tabeli Car. Na końcu wprowadzamy jeszcze tylko nazwę zapytania (np. RowCount) i klikamy na Finish. Następnie możemy dodać metodę zwracającą informację o samochodzie na podstawie jego Id. Tak jak poprzednio nadajemy nazwę metodzie i kończymy akcję kreatora.

Po dodaniu wymaganych zapytań, przechodzimy do edycji klas. Istnieją dwa podejścia do tego zadania. Pierwsze polega na wyedytowaniu stworzonych przez designer gotowych plików klas. Drugi to utworzenie pliku z nagłówkami klas i słówkiem kluczowym partial. Drugie rozwiązanie jest o tyle lepsze, że ułatwia czytanie kodu (kod wygenerowany przez designer może mieć tysiące linii).

Tworzymy więc w projekcie Code File a w nim kolejno tworzymy potrzebne namespaces oraz klasy i metody:

using System;

namespace DataAccessLayer
{
    public partial class CarSet
    {
        public partial class CarDataTable
        {
            public CarDataTable GetAllCars()
            {
                CarSetTableAdapters.CarTableAdapter adapter = new CarSetTableAdapters.CarTableAdapter();
                return adapter.GetData();
            }
        }

        public partial class CarRow
        {
            public CarDataTable GetCarInformation()
            {
                CarSetTableAdapters.CarTableAdapter adapter = new CarSetTableAdapters.CarTableAdapter();
                return adapter.CarById(this.IdCar);
            }
        }

        public partial class PersonDataTable
        {

        }

        public partial class PersonRow
        {
            public PersonDataTable GetPersonInformation()
            {
                CarSetTableAdapters.PersonTableAdapter adaper = new CarSetTableAdapters.PersonTableAdapter();
                return adaper.PersonById(this.IdPerson);
            }
        }
    }
}

namespace DataAccessLayer.CarSetTableAdapters
{
    public partial class CarTableAdapter
    {
        public CarSet.CarDataTable GetCars(int startRecord, int maxRecords, string orderColumn = null)
        {
            Adapter.SelectCommand = CommandCollection[0];
            CarSet carSet = new CarSet();
            if (!string.IsNullOrWhiteSpace(orderColumn))
            {
                Adapter.SelectCommand.CommandText += "ORDER BY " + orderColumn;
            }

            Adapter.Fill(startRecord, maxRecords, new System.Data.DataTable[] {carSet.Car});

            return carSet.Car;
        }
    }

    public partial class PersonTableAdapter
    {

    }
}

Tak więc dodając kolejne metody tworzymy warstwę danych. Teraz przejdziemy do warstwy logiki biznesowej.

Logika biznesowa jak wiemy z wcześniejszego akapitu odpowiada za warstwę komunikującą się z prezentacją i dostępem do danych. Metody implementowane w warstwie logiki biznesowej w większości zwracają DataSety z danymi.
Tworzymy więc nowy projekt w naszej solucji typu Class Library o nazwie np. BusinessLogicLayer. Dodajemy referencję do warstwy dostępu danych i w tak przygotowanym projekcie zaczynamy tworzyć klasy i metody odpowiedzialne za przetwarzanie danych.

W większości przypadków w warstwie logiki biznesowej tworzymy klasy mapujące te zawarte w warstwie dostępu do danych. W naszym przypadku będą to więc dwie klasy:
CarBL oraz PersonBL. Dodajemy odpowiednie klauzule using:

using DataAccessLayer;
using DataAccessLayer.CarSetTableAdapters;

Następnie tworzymy metody:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataAccessLayer;
using DataAccessLayer.CarSetTableAdapters;

namespace BusinessLogicLayer
{
    public class CarBL
    {
        private CarTableAdapter adapter = new CarTableAdapter();

        public CarSet.CarDataTable GetCars()
        {
            return adapter.GetData();
        }

        public CarSet.CarDataTable GetCustomerById(int i)
        {
            return adapter.CarById(i);
        }

        public int? GetRowsCount()
        {
            return adapter.RowCount();
        }
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataAccessLayer;
using DataAccessLayer.CarSetTableAdapters;

namespace BusinessLogicLayer
{
    public class PersonBL
    {
        private PersonTableAdapter adapter = new PersonTableAdapter();

        public CarSet.PersonDataTable GetAllPersons()
        {
            return adapter.GetData();
        }

        public CarSet.PersonDataTable GetPersonById(int i)
        {
            return adapter.PersonById(i);
        }

        public int? GetRowsCount()
        {
            return adapter.RowCount();
        }
    }
}


Oczywiście należy jeszcze dodać metody odpowiadające z usuwanie, dodawanie oraz modyfikowanie danych w bazie danych.

Oprócz tego klasy i metody możemy oznaczyć znacznikami znajdującymi się w przestrzeni System.ComponentModel, dzięki czemu w łatwy sposób będziemy mogli bindować kontrolki do warstwy biznesowej:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DataAccessLayer;
using DataAccessLayer.CarSetTableAdapters;
using System.ComponentModel;

namespace BusinessLogicLayer
{
    [DataObject]
    public class PersonBL
    {
        private PersonTableAdapter adapter = new PersonTableAdapter();

        [DataObjectMethod(DataObjectMethodType.Select, true)] //Komenta typu select; true - domyślna
        public CarSet.PersonDataTable GetAllPersons()
        {
            return adapter.GetData();
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public CarSet.PersonDataTable GetPersonById(int i)
        {
            return adapter.PersonById(i);
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public int? GetRowsCount()
        {
            return adapter.RowCount();
        }
    }
}



Czas teraz na ostatni etap tworzenia projektu - warstwa prezentacji. Do prezentacji danych Microsoft dostarcza wielu technologi: Silverlight, WindowsForms, ASP.NET, WPF itp. W tym projekcie zastosuję ASP.NET gdyż to aplikacje internetowe cieszą się największym zainteresowaniem w dzisiejszych czasach.

Tworzymy więc nowy projekt typu ASP.NET WebSite i dodajemy w nim referencję do warstwy biznesowej.

Następnie tworzymy pustą stronę i dodajemy do niej kontrolkę GridView. Należy teraz skonfigurować kontrolkę, aby czerpała dane z warstwy biznesowej. Wybieramy Choose Data Source a następnie New Data Source. W kreatorze który się utworzy wybieramy Object i nadajemy mu nazwę. Teraz z listy Choose your business object wybieramy interesującą nas klasę (PersonBL lub CarBL). Kolejno podpinamy metody dla SELECT, UPDATE, INSERT oraz DELETE. Po uruchomieniu aplikacji otrzymujemy tabelkę wypełnioną odpowiednimi danymi:


Tak więc całość po drobnych modyfikacjach, dodaniu kolejnych metod spowoduje, że rozwój projektu będzie prosty i szybki. Dzięki podziałowi można stworzyć kilka modułów z różną funkcjonalnością, lub nawet umożliwić ich wymianę w czasie działania programu. 

Opisany powyżej proces można przyśpieszyć poprzez zastosowanie nowszych technologi ORMów jak LINQu czy też Entity Framework. Jeżeli mówimy o aplikacjach internetowych można skorzystać z wzorca MVC (Model, View, Controler) który koncepcją nie odbiega od powyżej przedstawionego rozwiązania. 

Projekt można pobrać stąd.

Źródło:
http://en.wikipedia.org/wiki/Three-tier_%28computing%29#Three-tier_architecture
http://pl.wikipedia.org/wiki/Architektura_wielowarstwowa
http://pl.wikipedia.org/wiki/Architektura_tr%C3%B3jwarstwowa
http://www.codeproject.com/KB/architecture/Application_Architecture.aspx
http://www.codeproject.com/KB/aspnet/ASP_NET_MVC_WITH_EF.aspx
http://www.youtube.com/watch#!v=UJdRkr3tB5o&feature=related

Brak komentarzy:

Prześlij komentarz