W jaki sposób wzorzec używania programów obsługi poleceń do radzenia sobie z trwałością pasuje do czysto funkcjonalnego języka, w którym chcemy, aby kod związany z IO był jak najcieńszy?
Podczas implementowania projektowania opartego na domenie w języku obiektowym często stosuje się wzorzec polecenia / procedury obsługi do wykonywania zmian stanu. W tym projekcie programy obsługi poleceń znajdują się nad obiektami domeny i są odpowiedzialne za nudną logikę związaną z trwałością, taką jak używanie repozytoriów i publikowanie zdarzeń domeny. Procedury obsługi są publiczną twarzą Twojego modelu domeny; kod aplikacji, taki jak interfejs użytkownika, wywołuje programy obsługi, gdy musi zmienić stan obiektów domeny.
Szkic w C #:
public class DiscardDraftDocumentCommandHandler : CommandHandler<DiscardDraftDocument>
{
IDraftDocumentRepository _repo;
IEventPublisher _publisher;
public DiscardDraftCommandHandler(IDraftDocumentRepository repo, IEventPublisher publisher)
{
_repo = repo;
_publisher = publisher;
}
public override void Handle(DiscardDraftDocument command)
{
var document = _repo.Get(command.DocumentId);
document.Discard(command.UserId);
_publisher.Publish(document.NewEvents);
}
}
document
Obiekt domena jest odpowiedzialna za wdrażanie reguł biznesowych (takich jak „użytkownik powinien mieć uprawnienie do odrzucenia dokumentu” lub „nie można odrzucić dokument, który już został odrzucony”) oraz do generowania zdarzeń domen musimy publikować ( document.NewEvents
będzie być IEnumerable<Event>
i prawdopodobnie zawiera DocumentDiscarded
zdarzenie).
Jest to ładny projekt - można go łatwo rozszerzyć (można dodawać nowe przypadki użycia bez zmiany modelu domeny, dodając nowe programy obsługi poleceń) i jest agnostyczny, jeśli chodzi o sposób utrwalania obiektów (można łatwo wymienić repozytorium NHibernate dla Mongo repozytorium lub zamień wydawcę RabbitMQ na wydawcę EventStore), co ułatwia testowanie przy użyciu podróbek i prób. Przestrzega także separacji modelu / widoku - moduł obsługi poleceń nie ma pojęcia, czy jest używany przez zadanie wsadowe, interfejs GUI, czy interfejs API REST.
W czysto funkcjonalnym języku, takim jak Haskell, możesz modelować moduł obsługi poleceń mniej więcej tak:
newtype CommandHandler = CommandHandler {handleCommand :: Command -> IO Result)
data Result a = Success a | Failure Reason
type Reason = String
discardDraftDocumentCommandHandler = CommandHandler handle
where handle (DiscardDraftDocument documentID userID) = do
document <- loadDocument documentID
let result = discard document userID :: Result [Event]
case result of
Success events -> publishEvents events >> return result
-- in an event-sourced model, there's no extra step to save the document
Failure _ -> return result
handle _ = return $ Failure "I expected a DiscardDraftDocument command"
Oto część, którą staram się zrozumieć. Zazwyczaj istnieje jakiś rodzaj kodu „prezentacji”, który wywołuje moduł obsługi poleceń, na przykład GUI lub interfejs API REST. Więc teraz mamy w naszym programie dwie warstwy, które muszą wykonywać operacje wejścia / wyjścia - moduł obsługi poleceń i widok - co w Haskell jest dużym zakazem.
O ile mi wiadomo, istnieją tutaj dwie przeciwstawne siły: jedna to separacja modelu od widoku, a druga to potrzeba zachowania modelu. Musi istnieć kod IO, aby gdzieś utrwalić model , ale separacja modelu / widoku mówi, że nie możemy umieścić go w warstwie prezentacji z całym innym kodem IO.
Oczywiście w „normalnym” języku IO może (i tak się dzieje) wszędzie. Dobry projekt nakazuje, aby różne typy IO były oddzielone, ale kompilator tego nie wymusza.
Więc: jak pogodzić separację modelu / widoku z chęcią przesunięcia kodu IO na samą krawędź programu, kiedy model musi zostać utrwalony? Jak utrzymywać dwa różne typy IO osobno , ale wciąż z dala od całego czystego kodu?
Aktualizacja : Nagroda wygasa za mniej niż 24 godziny. Nie wydaje mi się, aby którakolwiek z obecnych odpowiedzi w ogóle odnosiła się do mojego pytania. @ Ptharien's Flame komentarz na temat acid-state
wydaje się obiecujący, ale nie jest to odpowiedź i brakuje w niej szczegółów. Nie chciałbym, żeby te punkty zmarnowały się!
źródło
acid-state
wydaje się być zbliżony do tego, co opisujesz .acid-state
wygląda całkiem świetnie, dzięki za ten link. Jeśli chodzi o projekt interfejsu API, nadal wydaje się, że jest to związaneIO
; moje pytanie dotyczy tego, jak struktura trwałości pasuje do większej architektury. Czy znasz jakieś aplikacje typu open source, które używająacid-state
obok warstwy prezentacji, i udało Ci się je rozdzielić?Query
iUpdate
monady są dość dalekieIO
. Spróbuję podać prosty przykład w odpowiedzi.Odpowiedzi:
Ogólnym sposobem oddzielania komponentów w Haskell są stosy transformatorów monadowych. Wyjaśnię to bardziej szczegółowo poniżej.
Wyobraź sobie, że budujemy system, który ma kilka dużych komponentów:
Zdecydowaliśmy, że musimy zachować luźne połączenie tych komponentów, aby zachować dobry styl kodu.
Dlatego kodujemy każdy z naszych składników polimorficznie, wykorzystując różne klasy MTL do prowadzenia nas:
MonadState DataState m => Foo -> Bar -> ... -> m Baz
DataState
to czysta reprezentacja migawki stanu naszej bazy danych lub pamięciMonadState UIState m => Foo -> Bar -> ... -> m Baz
UIState
jest czystą reprezentacją migawki stanu naszego interfejsu użytkownikaMonadState (DataState, UIState) m => Foo -> Bar -> ... -> m Baz
main :: IO ()
która wykonuje prawie banalną pracę polegającą na łączeniu pozostałych komponentów w jeden systemzoom
lub podobny kombinatorStateT (DataState, UIState) IO
, który jest następnie uruchamiany z rzeczywistą zawartością bazy danych lub pamięci do wytworzeniaIO
.źródło
DataState
jest czystą reprezentacją migawki stanu naszej bazy danych lub magazynu”. Prawdopodobnie nie masz zamiaru ładować całej bazy danych do pamięci!Czy model powinien zostać utrwalony? W wielu programach wymagane jest zapisanie modelu, ponieważ stan jest nieprzewidywalny, każda operacja może w dowolny sposób zmutować model, więc jedynym sposobem na poznanie stanu modelu jest bezpośredni dostęp do niego.
Jeśli w twoim scenariuszu sekwencja zdarzeń (polecenia, które zostały zatwierdzone i zaakceptowane) zawsze może wygenerować stan, to zdarzenia muszą zostać utrwalone, niekoniecznie stan. Stan można zawsze wygenerować, odtwarzając zdarzenia.
To powiedziawszy, często zdarza się, że stan jest przechowywany, ale tylko jako migawka / pamięć podręczna, aby uniknąć ponownego odtwarzania poleceń, a nie jako niezbędne dane programu.
Po zaakceptowaniu polecenia zdarzenie jest przekazywane do dwóch miejsc docelowych (pamięci zdarzeń i systemu raportowania), ale na tej samej warstwie programu.
Zobacz także
Event Sourcing
Chętna pochodna odczytu
źródło
Próbujesz umieścić miejsce w aplikacji intensywnie korzystającej z IO na wszystkie działania inne niż IO; niestety typowe aplikacje CRUD, o których mówisz, robią niewiele poza IO.
Wydaje mi się, że rozumiesz odpowiednią separację, ale tam, gdzie próbujesz umieścić kod IO trwałości na pewnej liczbie warstw z dala od kodu prezentacji, ogólny fakt jest w twoim kontrolerze, gdzieś powinieneś zadzwonić do swojego warstwa uporczywości, która może wydawać się zbyt blisko twojej prezentacji - ale to tylko zbieg okoliczności, że w tego typu aplikacjach nie ma nic więcej.
Prezentacja i wytrwałość składają się zasadniczo na cały rodzaj aplikacji, którą myślę, że tu opisujesz.
Jeśli myślisz w myślach o podobnej aplikacji, która zawiera dużo złożonej logiki biznesowej i przetwarzania danych, myślę, że możesz sobie wyobrazić, jak to jest ładnie oddzielone od prezentacji IO i uporczywych operacji IO, takich jak nie musi też nic wiedzieć. Problem, który masz teraz, jest po prostu postrzegalny, spowodowany próbą znalezienia rozwiązania problemu w typie aplikacji, która na początku nie ma tego problemu.
źródło
Tak blisko, jak potrafię zrozumieć twoje pytanie (czego mogę nie wiedzieć, ale pomyślałem, że wrzucę moje 2 centy), ponieważ niekoniecznie masz dostęp do samych obiektów, musisz mieć własną bazę danych obiektów, która wygasa z czasem).
Idealnie same obiekty można ulepszyć, aby zachowywały swój stan, więc gdy zostaną one „przekazane”, różne procesory poleceń będą wiedziały, z czym pracują.
Jeśli nie jest to możliwe (icky icky), jedynym sposobem jest posiadanie jakiegoś wspólnego klucza podobnego do DB, którego można użyć do przechowywania informacji w sklepie, który jest skonfigurowany do udostępniania różnych komend - i miejmy nadzieję, „otwórz” interfejs i / lub kod, aby inni pisarze poleceń przyjęli również interfejs do zapisywania i przetwarzania meta-informacji.
W obszarze serwerów plików samba ma różne sposoby przechowywania rzeczy, takich jak listy dostępu i alternatywne strumienie danych, w zależności od tego, co zapewnia system operacyjny hosta. Idealnie, samba jest hostowana w systemie plików i zapewnia rozszerzone atrybuty plików. Przykład „xfs” na „linux” - więcej poleceń kopiuje atrybuty rozszerzone wraz z plikiem (domyślnie większość programów na Linuksie „dorastała” bez atrybutów rozszerzonych).
Alternatywnym rozwiązaniem - które działa dla wielu procesów samby od różnych użytkowników działających na wspólnych plikach (obiektach), jest to, że jeśli system plików nie obsługuje dołączania zasobu bezpośrednio do pliku, jak w przypadku atrybutów rozszerzonych, używa modułu, który implementuje wirtualna warstwa systemu plików do emulacji rozszerzonych atrybutów dla procesów samby. Tylko samba o tym wie, ale ma tę zaletę, że nie obsługuje formatu obiektowego, ale nadal działa z różnymi użytkownikami samby (patrz procesory poleceń), którzy pracują nad plikiem w oparciu o jego poprzedni stan. Będzie przechowywać meta informacje we wspólnej bazie danych dla systemu plików, co pomaga kontrolować rozmiar bazy danych (i nie „
Może ci się to nie przydać, jeśli potrzebujesz więcej informacji specyficznych dla implementacji, z którą pracujesz, ale koncepcyjnie ta sama teoria może być zastosowana do obu zestawów problemów. Więc jeśli szukasz algorytmów i metod do robienia tego, co chcesz, może to pomóc. Jeśli potrzebujesz bardziej szczegółowej wiedzy w określonych ramach, być może nie jest tak pomocny ... ;-)
BTW - powód, dla którego wspominam o „wygasającym” - jest to, że nie jest jasne, czy wiesz, jakie obiekty są na zewnątrz i jak długo się utrzymują. Jeśli nie masz bezpośredniego sposobu, aby dowiedzieć się, kiedy obiekt jest usuwany, musisz przyciąć swój własny metaDB, aby zapobiec wypełnieniu go starymi lub starożytnymi meta informacjami, dla których użytkownicy już dawno usunęli te obiekty.
Jeśli wiesz, kiedy obiekty wygasają / są usuwane, oznacza to, że jesteś przed grą i możesz jednocześnie wygasić ją z metaDB, ale nie było jasne, czy masz taką opcję.
Twoje zdrowie!
źródło