2009-09-24 14 views
5

W jednej z moich bieżących aplikacji potrzebuję uzyskać dane klienta ze zdalnej usługi (CRM) za pośrednictwem usługi Webservice/SOAP. Ale chcę również buforować dane w bazie danych mysql, więc nie będę musiał łączyć się z serwisem internetowym po raz kolejny (dane nie zmieniają się często, usługa zdalna jest wolna i ma limit przepustowości - więc buforowanie jest w porządku/musi).Uzyskiwanie danych z usługi zdalnej, jeśli nie jest ona zapisana w pamięci podręcznej w bazie danych - potrzebna sugestia

Jestem dość pewien technicznej części tego zadania, ale nie jestem pewien, jak zaimplementować to czyste i przejrzyste w mojej aplikacji internetowej.

Wszystkie moje pozostałe dane pochodzą z jednej bazy danych mysql, więc mam repozytoria, które zwracają listy lub pojedyncze jednostki zapytane z bazy danych za pomocą NHibernate.

Moje pomysły do ​​tej pory:


1 Wszystko w jednym

Użyj CustomerRepository, która wygląda dla klienta przez ID, czy udany, a następnie odesłać go, jeszcze wywołać usługa i zapisz pobrane dane do bazy danych.

Kontroler wygląda następująco:

class Controller 
{ 
    private CustomerRepository Rep; 

    public ActionResult SomeAction(int id) 
    { 
     return Json(Rep.GetCustomerById(id)); 
    } 
} 

Repository w pseudo/prosty kod tak:

class CustomerRepository 
{ 
    public Customer GetCustomerById(int id) 
    { 
     var cached = Database.FindByPK(id); 
     if(cached != null) return cached; 

     var webserviceData = Webservice.GetData(id); 
     var customer = ConvertDataToCustomer(webserviceData); 

     SaveCustomer(customer); 

     return customer; 
    } 
} 

Chociaż powyższe wygląda nieco proste, myślę, że klasa CustomerRepository wzrośnie dość duży i brzydki . Więc nie podoba mi się to podejście.

Repozytorium powinno ładować tylko dane z bazy danych, co powinno być jego "kontraktem" przynajmniej w mojej aplikacji.


2 sepereate i naklejane razem w kontrolerze

użyć klas oddzielne dla repozytorium (dostęp db) i usługa (zdalny dostęp) i niech kontroler do pracy:

Controller wygląda tak:

class Controller 
{ 
    private CustomerRepository Rep; 
    private Webservice Service; 

    public ActionResult SomeAction(int id) 
    { 
     var customer = Rep.GetCustomerById(id); 
     if(customer != null) return Json(customer); 

     var remote = Service.GetCustomerById(id); 
     Rep.SaveCustomer(remote); 

     return Json(remote); 
    } 
} 

Chociaż wygląda to nieco lepiej, nadal nie lubię wstawiać wszystkich logi c w kontrolerze, ponieważ obsługa błędów, gdy usługa nie zwraca danych, jest pomijana i prawdopodobnie może zająć trochę więcej rzeczy.

Może mógłbym utworzyć kolejną warstwę usługi używaną przez kontroler, ale kod byłby zupełnie taki sam, ale w innej klasie.

Właściwie chciałbym, aby mój kontroler używał pojedynczego interfejsu/klasy, który hermetyzuje te rzeczy, ale nie chcę jednej klasy, która "robi to wszystko": dostępu do repozytorium, dostępu do usługi sieciowej, zapisywania danych. . czuje się trochę źle do mnie ...



Wszystkie pomysły do ​​tej pory są/będą prawdopodobnie się dość wleczenia z kodem buforowania, obsługi błędów itp myślę.

Może mógłbym posprzątać kilka rzeczy za pomocą AOP?

W jaki sposób jesteś zaimplementować rzeczy takie jak powyżej?

Zastosowane struktury techniczne: ASP.NET MVC, Spring.NET dla DI, NHibernate jako ORM, mysql jako baza danych, usługa zdalna jest dostępna za pośrednictwem protokołu SOAP.

Odpowiedz

5

Możesz całkowicie zaimplementować swoją architekturę za pomocą wzoru Decorator.

Wyobraźmy sobie, że masz interfejs o nazwie ICustomerRepository.

Najpierw zaimplementuj ICustomerRepository (nazwijmy to WsCustomerRepository) w oparciu o usługę internetową i nie martw się o bazę danych w ogóle w tej implementacji.

Następnie zaimplementujesz innego ICustomerRepository (MySqlRepository), który tylko mówi do twojej bazy danych.

Na koniec utworzysz trzeci ICustomerRepository (CachingRepository), który implementuje logikę buforowania, którą opisujesz. Zaczyna się od zapytania do "lokalnego" repozytorium, ale jeśli nie otrzyma wyniku, wysyła zapytanie do "zdalnego" repozytorium i wstawia wynik do "lokalnego" repozytorium, zanim zwróci wynik.

Twój CachingRepository mógłby wyglądać następująco:

public class CachingRepository : ICustomerRepository 
{ 
    private readonly ICustomerRepository remoteRep; 
    private readonly ICustomerRepository localRep; 

    public CachingRepository(ICustomerRepository remoteRep, ICustomerRepository localRep) 
    { 
     this.remoteRep = remoteRep; 
     this.localRep = localRep; 
    } 

    // implement ICustomerRepository... 
} 

Jak widać, CachingRepository nawet nie muszą wiedzieć o konkretnej usługi internetowej i bazy danych, ale może skoncentrować się na jej jednolitego odpowiedzialności: buforowanie

Daje to czysty podział odpowiedzialności, a jeśli chodzi o kontrolerów, rozmawiają one tylko z instancją ICustomerRepository.

+1

Ooh, podoba mi się twoja odpowiedź nawet bardziej niż moja! Miły. –

+0

to naprawdę interesujące podejście. – Max

+0

@Max: Decorator to wzór, który widzi wiele zastosowań w DI - między innymi w scenariuszach dokładnie takich jak ten :) –

0

Jest to idealny kandydat do warstwy serwisowej, moim zdaniem.

Masz dwa źródła danych (usługa DB Repo i usługa sieci Web) i musisz je hermetyzować dla kontrolera zgodnie z sugestiami.

Jeśli warstwa usługa ma metodę taką, że kontroler może wywołać coś takiego:

public class Controller 
{ 
    private CustomerService _customerService; // Perhaps injected by an IoC container? 

    public ActionResult SomeAction(int id) 
    { 
     return Json(_customerService.GetCustomerById(id)); 
    } 
} 

Następnie warstwę usługa może obsłużyć logiki biznesowej/buforowanie jak tak (można wstrzyknąć repozytorium):

public class CustomerService 
{ 
    private readonly ICustomerRepository _customerRepository; 

    public CustomerService(ICustomerRepository customerRepository) 
    { 
     _customerRepository = customerRepository; 
    } 

    public Customer GetCustomerById(int id) 
    { 
     var cached = _customerRepository.FindByPK(id); 
     if(cached != null) return cached; 

     var webserviceData = Webservice.GetData(id); // You could inject the web service as well... 
     var customer = ConvertDataToCustomer(webserviceData); 

     _customerRepository.SaveCustomer(customer); 

     return customer; 
    } 
} 

Następnie można zachować repozytorium i logikę usługi sieciowej w oddzielnych klasach lub (jeszcze lepiej) w oddzielnych złożeniach.

Nadzieję, że pomaga!

+0

Myślałem również o tym podejściu: jaka byłaby korzyść z używania warstwy usługowej zamiast wprowadzania wszystkiego do kontrolera/metody działania? To, czego nie jestem pewien. – Max

+1

Zaletą jest to, że utrzymuje logikę biznesową poza sterownikiem i umożliwia ponowne użycie metody warstwy usług w innym kontrolerze, jeśli to konieczne. –

0

Osobiście w tym scenariuszu ponownie rozważyłbym twój projekt. Zastanowiłbym się nad oddzielnym procesem (być może usługą Windows), który aktualizuje MySql DB ze zdalnej usługi.

W ten sposób klienci mogą zawsze uzyskać dane z lokalnej bazy danych bez konieczności blokowania przez znaczną ilość czasu. W razie potrzeby nadal można używać buforowania podczas pobierania danych z bazy danych MySql.

Zgaduję, czy to podejście jest możliwe, zależy od tego, ilu klientów masz. Podejście to sprawdziło się kilka lat temu, gdy uzyskiwałem kursy wymiany z usług internetowych, które nie były szczególnie niezawodne.

0

Generalnie podoba mi się odpowiedź Marka, ale chciałbym dodać dwie ważne uwagi.

  1. Jeśli nawet buforowanie to w DB? Dla mnie jest to idealne wykorzystanie pamięci podręcznej aplikacji. Zazwyczaj mam interfejs buforowania, który hermetyzuje pamięć podręczną aplikacji HTTP i ma łatwe w użyciu metody, które automatycznie sprawdzają istnienie klucza, który wywołują twoją metodę i umieszczają w pamięci podręcznej. Możesz także umieścić na nim wygaśnięcie, aby automatycznie wygasać po pewien czas.

    var result = CacheManager.Add (() => YourMethodCall(), "CachedStuffKey", nowy przedział czasowy (0,1,0));

Wiem, że to wydaje się skomplikowane, ale przechowywanie go w pamięci podręcznej będzie szybsze pod względem wydajności i pozbędzie się konieczności tworzenia nowego stołu.

  1. Zgadzam się, że ten typ logiki naprawdę nie należy do kontrolera, kontroler powinien być dość głupi i być w większości klejem pomiędzy twoim interfejsem a niższymi warstwami. Chociaż zależy to od rodzaju tworzonej aplikacji. Nie wszystkie aplikacje muszą mieć 3 warstwowe super struktury.
Powiązane problemy