System składkowy

To jest coś do czego mam lekkie obawy. Okay, widoki, parsowanie, notyfikacje, wprowadzanie danych, to jest proste do tworzenia i refactoringu.

Uważam jednak, że zła implementacja logu transakcji może utrudnić refaktoryzacje. Chociaż pewnie są techniki jak to robić sensownie. Z drugiej strony właśnie stosowanie event sourcing powinno umożliwiać wprowadzanie właśnie tych zmian. Mając cała historię akcji, można ją odtworzyć stosując nową logikę biznesową.

Poza tym nie wiemy co przyniesie przyszłość.

Jaki JSON? Dane produkowane przez system żyją dłużej niż sam system. Zamiast wymyślać coś nowego, można zastosować jakiś standard. Na przykład OFX ma już sporo napisanych adapterów z różnych formatów bankowych. Dodatkowo, sam system może zostać kiedyś zaorany i zastąpiony czymś innym. Z dużą szansą, że poprzednie interfejsy będą musiały być zachowane.

Pora zakasać rękawy. Na początek warto zbudować jakąś podstawową strukturę. Logowanie przez SSO, widoki użytkowników. Obsługa składek będzie najpewniej osobnym modułem w którym spróbuję zamknąć cały event sourcing. Ale najpierw muszę zrobić jakiś prototyp obok.

Ale w międzyczasie można coś specyfikować:

@yakub ogarniasz django? Jakie znaczenie ma wybór między 4, a 3.2?

1 Like

Mogę coś tam w Django ruszyć, robiłem w tym pracę inżynierską i mam z tym czasem styczność w pracy.
4 to jest dopiero release candidate i na razie bym się na to nie rzucał, nie wiem jakie są zmiany, ale wolę się nie zaskoczyć tym, że jakaś biblioteka np. do OAuth nie działa jeszcze z wersją 4.0.

Ogólnie można myśleć o hspsh/meta jako kodzie smugowym

Tutaj z znajdują się moje eksperymenty z event sourcingiem. Udało mi się zaimplementować składki.

Agregat Fee wylicza wysokość składki przez podanie do niego przedziału czasu. Pozwala też na zmianę składki i jej zmiana działa od ustalonego momentu.

@psuwala, możesz przejrzeć i ocenić jak się podoba

Nazywam się jak się nazywam, ale tez chcę dorzucić swoje 3 grosze:

  • nie używaj typów zmiennoprzecinkowych do reprezentowania pieniędzy. Ustał, ze każdą wartość mnożysz przez stałą i trzymaj w bazie liczby całkowite. Bez tego ryzykujesz gubienie groszy przez błędy zaokrągleń.
  • jeśli zdefiniowałeś zdarzenie credit, to sensowne byłoby tez zdarzenie debit wyzwalane 1 każdego miesiąca o 00:00. W ten sposób łatwo będzie ustalić, kto zalega ze składkami.
  • nie rozumiem zdarzenia Opened da Fee. Rozumiem dla Account, ale nie dla Fee. To byłoby coś na kształt obciążenia składką 1 każdego miesiąca? Ale to powinno być z kolei zdarzenie na koncie, nie na Fee.
  • get_dues na Fee powinno raczej być wołanie na Account. Do tego zdarzenie debit i wio! :wink:

Sorry, ze nie na GH, ale z aplikacji nie da się, chyba, komentować commitów.

W kwestii

Czy pythonowy typ Decimal nie daje już takich gwarantów?

Zastanawiam się czy mogę uniknąć wywoływania polecenia debit każdego miesiąca. Taki był mój pomysł na ten agregat Fee, że on wylicza należną składkę od np. otwarcia konta do teraz. Chciałbym uniknąć sytuacji, gdzie system w sposób automatyczny zmienia stan kont. Jedynym wyjątkiem powinno być wprowadzanie danych z przelewów.

To pierwsze zdarzenie które po prostu definiuje dany typ składki, oraz jej początkową wysokość.

W każdym razie dzięki za komentarze. Niby mając już te wszystkie zdarzenia nie powinienem się bać obciążać kont, bo zawsze mogę przywrócić stan poprzedni. Ale coś w automatycznym naliczaniu należności z poziomu Fee wydaje się bardziej eleganckie.

Skąd będziesz wiedział kto ile zalega?

Faktycznie, powinien.

Zdradź jeszcze jak to będzie w bazie zapisane? Bo z tego co pamietam, to przy event sourcingu źródłem prawdy powinny być zdarzenia. Zawołanie Account.credit() spowoduje zapisanie zdarzenia do bazy?

Przepraszam za pińcet komentarzy; piszę z telefonu.

Myślałem, że zrealizuje to w ten sposób:

ile_kto_zalega(czas_od_otwarcia_konta) == fee.get_dues(czas_od_otwarcia_konta) + acc.balance

Chyba będą tam jeszcze inne szególne przypadki. Ale właśnie próbuję je w ten sposób odkryć.

Biblioteka evensourcing ma event_store w którym zapisuje wszsystkie wydarzenia. I agregaty są budowane z tych wydarzeń przy zaciąganiu z bazy. Tam są jakieś nawet sensowne adaptery do baz danych w tej libce.

Zważ, że mając zdarzenie Debited wystarczy Ci znajomość stanu konta. IMHO to duże uproszczenie.

EDIT: pytanie pułapka: członek obniżył wpłacaną składkę z 200 do 100 złotych. Co się stanie z przedstawionym przez Ciebie wzorem?

Możliwe, że sensowniejszym byłby jakiś mechanizm wyliczający składkę w danym momencie, i jej aplikowanie przez wydarzenie Debited.

Jeśli zmniejszył wpłacaną, to zostanie naliczony dług. Jeśli ustali inną wartość składki, to będzie to uznane:

fee_id = app.create_fee(name="account_specific", amount=Decimal(100))

# collect fees for one year
assert app.get_dues(fee_id, datetime(2020,1,1), datetime(2020,12,30)) == Decimal(1200)

# collect fees for one year but double amount mid-year
app.set_amount(fee_id, Decimal(200), datetime(2020,7,1))
assert app.get_dues(fee_id, datetime(2020,1,1), datetime(2020,12,30)) == Decimal(1800)

Skąd będziesz wiedział kiedy się kwota zmieniła? Dodasz pola from i to Fee? Czy może jakieś inne jeszcze zdarzenie? IMHO należałoby całość zoptymalizować pod główną funkcjonalność, czyli ustalanie ile kto wpłacił i ile kto wisi.

Może faktycznie pozbędę się na ten moment tego Fee i wstawię coś na sztywno. Chociaż ten agregat działa całkiem sprawnie i bez problemu śledzi zmiany wysokości składki.

To co mnie ciekawi, to jak naliczać obciążenia bez crona. Aby było tylko jedno obciążenie na miesiąc. Możemy sobie wyobrazić jakiegoś crona który odpali się dwa razy w związku ze zmianą czasu na letni. Albo kilka triggerów z różnych źródeł nieświadomych, że nakazują naliczyć coś dwa razy. To znaczy, można mieć jakieś wyjątki które by do tego nie dopuściły, ale może jest sposób aby agregat sam o to dbał?

Skąd w takim razie wiesz, kiedy zmieniła się wysokość składki? Widzę, ze czegoś nie łapię mocno :stuck_out_tongue:

O tu tu, zapisuję również moment w którym wprowadzono zmianę. I pod tym mam raczej ułomny kod który jakoś odtwarza historię i nalicza składkę ze zmianą wielkości. Obawiam się, że robię coś nie według sztuki.

https://github.com/not7cd/solid-octo-journey/blob/dce24725afdd353d33f4dab245a6ced6f68c5a99/fees.py#L24-L28

Dobra, już kumam. IMHO lepiej byłoby to zrobić czysto rasowo na zdarzeniach - mniej pitolenia z wyciąganiem tych danych. Oraz: porównując ten system do bardzo uproszczonego banku, to biorąc kredyt hipoteczny masz listę zdarzeń debited na następnych 20 lat i dorabiasz do nich zdarzenia credited. Jakoś bardziej mi się to trzyma kupy (ale to mi, nie jestem nieomylną wyrocznią itp, itd).
Mógłbyś się nawet pokusić o generowanie zdarzeń zmiany wysokości deklarowanych składek, żeby już wiedzieć wszystko i zawsze :slight_smile:

W takim razie przygotuje kolejny prototyp. Z bardziej koszernym modelem.

Nadal zastanawia mnie jak obsłużyć różne “plany” składek. Oraz to jak się zmieniają w wysokości i relacji do członków.

Przynajmniej nauczyłem się trawersować historię.

Masz na myśli to, że niektórzy chcą np płacić kwartalnie? Debited księgowane raz na kwartał. Logika decydowania o tym kiedy komu kwartał się zaczyna musi być zależna od daty rozpoczęcia członkostwa.