niedziela, 29 sierpnia 2010

IRepository Pattern - wzorzec repozytorium

Repository Pattern to jeden z ważniejszych wzorców w przypadku pisania aplikacji ASP.NET MVC.
Podczas pisania aplikacji w MVC, w której wykorzystujemy TDD często natrafiamy na problem testowania warstwy bazodanowej. Najczęściej są to dwa podstawowe problemy: szybkość oraz odwracanie zmian które zaszły podczas testowania dodawania/usuwania itp. Problemy te często przekładają się na zniechęcenie do tworzenia testów, jak i sporym utrudnieniu tworzenia testowalnych aplikacji.
Potrzebne jest więc rozwiązanie, które pozwoli na łatwe testowanie kodu na obiektach w pamięci, jak i łatwą integrację w późniejszym stadium z bazą danych. Wszystkie te problemy może rozwiązać implementacja wzorca Repository Pattern.

Realny problem:

W przykładzie uczestniczą dwie klasy: Person oraz Car. Klasa Person reprezentuje jakiegoś człowieka, klasa Car - samochód.
Założenia: aplikacja ma pracować z bazą danych i mieć możliwość wykonywania testów bez potrzeby łączenia się do niej.
Wykorzystamy więc tutaj wzorzec repozytorium. Trochę teorii na początek. Wzorzec repozytorium zakłada utworzenie osobnego repozytorium dla każdej z klas. Repozytorium porozumiewa się z naszymi zmapowanymi obiektami i wykonuje żądania użytkownika.
W naszym przykładzie możemy łatwo wyodrębnić szereg metod które będą wykorzystywane np. na obiekcie klasy Person:
AddPerson, DeletePersonById, UpdatePerson, GetAllPersons, GetPersonById, GetPersonCount itd.
Druga z klas - Car będzie mieć podobne metody.
Istnieje więc szereg podobnych metod. Możemy je wyodrębnić jako wspólny interfejs IRepository. Interfejs ten będzie bazowym dla pozostałych interfejsów: IPersonRepository, ICarRepository.
Następnie stworzymy konkretne klasy implementujące nasze interfejsy: FakePersonRepository, SqlPersonRepository, FakeCarRepository, SqlCarRepository. Zapewni nam to łatwość przy testowaniu przy użyciu frameworków NUnit oraz Mocq.

Tak więc po kolei. Kod dwóch wcześniej omówionych klas:

    public class Person
    {
        public int IdPerson { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int BirthYear { get; set; }

        public List<Car> Cars { get; set; }
    }


    public class Car
    {
        public int IdCar { get; set; }
        public string Mark { get; set; }
        public int ProductionYear { get; set; }
    }

Teraz tworzymy interfejs IRepository:

Kod:

    public interface IRepository<T>

    {

        List<T> GetAll();

        T GetById(int id);

        int Count();

 

        void Add(T entity);

 

        void Update(T entity);

 

        void DeleteById(int id);

        void DeleteAll();

    }


    public interface IPersonRepository : IRepository<Person>

    {

        void AddCarToPerson(Person person);

    }


    public interface ICarRepository : IRepository<Car>

    {

    }


Tworzymy następnie klasy implementujące ICarRepository oraz IPersonRepository w wersji działającej w pamięci:

    public class FakeCarRepository : ICarRepository

    {

        private List<Car> _cars = new List<Car>();

 

        public List<Car> GetAll()

        {

            return _cars;

        }

 

        public Car GetById(int id)

        {

            return _cars.Single(x => x.IdCar == id);

        }

 

        public int Count()

        {

            return _cars.Count;

        }

 

        public void Add(Car entity)

        {

            _cars.Add(entity);

        }

 

        public void Update(Car entity)

        {

            Car c = _cars.Single(x => x.IdCar == entity.IdCar);

            c.Mark = entity.Mark;

            c.ProductionYear = entity.ProductionYear;

        }

 

        public void DeleteById(int id)

        {

            _cars.Remove(_cars.Single(x => x.IdCar == id));

        }

 

        public void DeleteAll()

        {

            _cars.Clear();

        }

    }


    class FakePersonRepository : IPersonRepository

    {

        private List<Person> _personList = new List<Person>();

 

        public List<Person> GetAll()

        {

            return _personList;

        }

 

        public Person GetById(int id)

        {

            try

            {

                return _personList.Where(x => x.IdPerson == id).SingleOrDefault();

            }

            catch (ArgumentNullException)

            {

                return null;

            }

        }

 

        public int Count()

        {

            return _personList.Count;

        }

 

        public void Add(Person entity)

        {

            _personList.Add(entity);

        }

 

        public void Update(Person entity)

        {

            Person p = _personList.Where(x => x.IdPerson == entity.IdPerson).SingleOrDefault();

            p = entity;

        }

 

        public void DeleteById(int id)

        {

            _personList.Remove(_personList.Where(x => x.IdPerson == id).SingleOrDefault());

        }

 

        public void DeleteAll()

        {

            _personList.Clear();

        }

 

        public void AddCarToPerson(Person person, Car car)

        {

            if (person.Cars == null)

            {

                person.Cars = new List<Car>();

            }

 

            person.Cars.Add(car);

        }

    }


W podobny sposób tworzymy klasy SqlPersonRepository. W tym przypadku jednak tworzymy klasę, która działa na bazie danych a nie w pamięci. Implementacja rozwiązania bazodanowego zależy przede wszystkim od wybranej technologi czy też ORM mappera.

Nasze rozwiązanie jest bardzo proste w dalszej pielęgnacji. Przy testowaniu korzystamy z naszych Faków, dla klienta dostarczamy wersję działającą bezpośrednio na bazie danych.

Brak komentarzy:

Prześlij komentarz