Wpisy, których autorem jest Teo Vincent Artur Wincenciak

Software engineer writing code in .NET and a conference organizer leading the Cracow .NET Developers Group. I have also been a university teacher as an additional activity. In my free time, I develop open-source projects and enjoy skiing, table tennis, and snooker as my big hobby. I am a fan of Ronnie O'Sullivan and Judd Trump. .NET, C#, C++, SOLID

Modelowanie HTTP REST API dla skomplikowanych procesów biznesowych czerpiące inspirację z RPC

Modelowanie HTTP API na kształt Remote Procedure Call (RPC)

Proponuję, aby dla skomplikowanych procesów budować HTTP API w sposób przypominający zdalne wywoływanie procedur.

Innymi słowy, dla skomplikowanych procesów biznesowych obejmujących wiele zasobów, możemy rozważyć sam proces biznesowy jako zasób.

REST API Design – Resource Modeling
Thoughtworks

CRUD vs Business Operations

W pierwszej kolejności, zanim zaczniesz czytać dalej, koniecznie przeczytaj ten wpis o projektowaniu REST API Design – Resource Modeling od Thoughtworks.

RPC & SOA vs REST & CRUD over HTTP API

To był wcześniejszy tytuł tego tekstu, który nie za bardzo się kliknął, ale zostawiłem go, ponieważ jeśli już ktoś dotarł tutaj, to ten tytuł w maksymalnie skondensowanej formie wyjaśnia na czym polega pomysł rozwiązania i równie zwięźle podsumowuje wyżej linkowany artykuł o projektowaniu REST API.


Domena

Jako przykład operacji biznesowej posłuży mi domena nauki wraz z jej artykułami naukowymi, które podlegają procesowi recenzji przed opublikowaniem. W tej domenie miałem okazję pracować przez ostatnie dwa lata.

Praca naukowa, która pomyślnie przeszła proces recenzji jest publikowana w czasopiśmie naukowym. Manuskryptem będziemy nazywać pracę naukową, która jest w trakcie recenzji. W celu przeprowadzenia recenzji, recenzenci są zapraszani dla konkretnego manuskryptu. Oni decydują o jego akceptacji lub odrzuceniu. Manuskrypt zaakceptowany jest następnie wysyłany do czasopisma naukowego, aby mógł zostać opublikowany jako artykuł.


Implementacja

Ogólny wzorzec operacji biznesowych na poziomie API przedstawia się następująco: na poziomie zasobów (resources) dodaje się nowy zasób o nazwie „actions„. Następnie, w obrębie „actions„, dodawana jest konkretna operacja biznesowa, czyli czasownik (action) w formie rozkazującej.

POST /{resource}/{id}/actions/{action}

Przykładem synchronicznej operacji biznesowej może być zaproszenie recenzenta oraz akceptacja lub odrzucenie manuskryptu. Każda z tych operacji biznesowych może być realizowana przez inny proces, który zależy od konfiguracji specyficznej dla poszczególnych magazynów naukowych.

POST /manuscripts/256/actions/invite

POST /manuscripts/256/actions/accept

POST /manuscripts/256/actions/reject

Sync

W odpowiedzi zwracany jest status 204 (No Content).

204 No Content

Async

W odpowiedzi zwracany jest status 202 (Accepted), a w nagłówku „Location” podawany jest link, za pomocą którego można sprawdzić status realizowanego procesu.

202 Accepted
Location: /{resources}/{id}/actions/{action}/{actionId}

Powodem modelowania asynchronicznej operacji biznesowej może być proces, w którym wymagane są więcej niż jeden krok (na przykład dodatkowa zgoda redaktora magazynu naukowego) lub gdy operacja jest czasochłonna i nie oczekuje się, że klient będzie czekać na jej zakończenie.

Przykładowo, publikację artykułu naukowego w magazynie naukowym można zamodelować w ten sposób.

POST /journals/16384/actions/submit

Status: 202 Accepted
Location: /journals/16384/actions/submit/4096

Następnie klient, korzystając z otrzymanego linku, wykonuje zapytanie.

GET /journals/16384/actions/submit/4096

W przypadku, gdy akcja nadal jest w trakcie wykonywania, wówczas zwracany jest status 102 (Processing) wraz z nagłówkiem „Retry-After„, który informuje, kiedy klient powinien wykonać ponowne sprawdzenie statusu.

102 Processing
Retry-After: 30

W przypadku, gdy akcja została wykonana w całości, wówczas zwracany jest status 204 (No Content).

204 No content

Sync lub Async

W szczególnych przypadkach możemy dać klientowi możliwość wyboru, czy dana operacja ma być wykonywana synchronicznie, czy asynchronicznie.

Operacja zostanie wykonana synchronicznie, jeśli klient podczas wywoływania API ustawi nagłówek „Expect” z wartością statusu 204 (No Content).

POST /journals/16384/actions/submit
Expected: 204-no-content

Operacja zostanie wykonana asynchronicznie, jeśli klient podczas wywoływania API ustawi nagłówek ‚Expect’ z wartością statusu 202 (Accepted).

POST /journals/16384/actions/submit
Expected: 202-accepted

W przypadku, gdy wybrany przez klienta oczekiwany sposób wykonania nie jest dostępny, wówczas akcja zwraca status 417 (Expectation Failed).

417 Expectation Failed

Acknowledgement

W ostatnim roku pracowałem nad projektem, w którym modelowaliśmy HTTP API w stylu Remote Procedure Call (RPC). Architektem i pomysłodawcą tego podejścia był Konrad Kwiatkowski, który kompletnie zmienił moje postrzeganie świata, przekonując mnie, że nie musimy dążyć do osiągnięcia czwartej, a nawet półtorej wersji dojrzałości modelu Richardsona dla REST API. Za tę perspektywę jestem mu niezmiernie wdzięczny.

Zaoszczędź swój czas, niech kod wyczyści się sam – ReSharper CLI CleanupCode GitHub Action

Wyluzujcie z tym kodem, mam coś, co zrobi za Was całą brudną robotę. CleanupCode Command-Line Tool w połączeniu z GitHub Actions – to jest to. Wystarczy kilka kliknięć i… voilà, kod sam się czyści. W README repo ReSharper CLI CleanupCode znajdziecie wszystko, czego potrzeba, żeby to ustawić.

Rzućcie okiem na ten link: ReSharper CLI CleanupCode – tak, to moje dzieło, które rok temu wylądowało na GitHub Marketplace. To właśnie tam zaczarowałem GitHub Actions, żeby sprzątały kod za Was.

Zajrzyjcie do projektu demo ReSharper CLI CleanupCode GitHub Action Demo. Tam znajdziecie przykład użycia tej GitHub Action wraz z instrukcją w pliku README. Idealne na start.

Minął rok od stworzenia tej GitHub Action, a teraz czas na wnioski.

Nasze team’y używają tego na Azure DevOps Server już od roku i nie ma dnia, żebyśmy nie podziękowali sobie za tę decyzję. Dwa zespoły, jeden kod – czysty jak łza. I nie tylko my to kumamy. Krzysiek Seroka „Code inspection on build server using ReSharper Command Line Tools”, nawet Jarek Stadnicki „tu ma być spacja” – wszyscy wiedzą, że porządny kod to podstawa.

Jeśli ciekawi Was, jak naprawdę działa ta GitHub Action, sprawdźcie repo Blef, mój open source projekt robiony po godzinach.To właśnie tam też, wśród skomplikowanych workflows GitHub, testowałem to rozwiązanie przez ostatni rok. Jest to open source więc mam możliwość podzielić się z Wami tym kawałkiem pracy. Zapraszam do repo Blef – i może nawet do dołączenia do gry?

Więc dajcie sobie szansę na nowe lepsze życie. W końcu kodowanie ma być przyjemnością, a nie sprzątaniem. Niech kod czyści się sam a wy miejcie czas na to, co naprawdę lubicie. Odpalcie sobie tego CleanupCode’a i GitHub Actions, a potem już tylko relaks i kodowanie, jak lubicie!

Jakby co, piszcie, chętnie podzielę się większymi szczegółami!

Update 03-01-2024: Zamieszczam link do rozmowy na Twitterze, która stanowi znakomite uzupełnienie informacji o narzędziu oraz zawiera cenne spostrzeżenia dotyczące samego konceptu. Dla osób, które nie posiadają konta na Twitterze, każda z odpowiedzi została opublikowana osobno.

Update 04-01-2024: Jeden z problemów, który rozwiązuje ta GitHub Action.

Update 04-01-2024: Narzędzie/podejście/metoda ta przybliża do osiągnięcia tego co Arkency opisali na swoim blog poście Disadvantages of Pull Requests. Nomen omen, bardzo polecam przeczytać.

Dump Wspomnień KGD .NET

KGD_LOGOSpotykamy się w bardzo luźnej atmosferze, gdzie w trakcie spotkania możemy poczęstować się darmową pizzą oraz browarem. Rozmowy mają charakter nieformalny. Zdobywamy nową wiedzę, nie zapominając o miłej atmosferze.

Po zakończeniu drugiej prelekcji często zostajemy na dłużej, aby nawiązać nowe znajomości oraz wymienić spostrzeżenia, często również są ścisłe, specjalistyczne, techniczne i często trudne z dziedziny programowania.

Prelegenci

Nagrody

100 spotkanie KGD .NET

10th Making Software & 100th KGD .NET – Kraków 2016

Ref Returns, Ref Locals, Ref Arg

Powracamy do podstaw, tak – dla sportu.

Przekazywanie zmiennej typu wartościowego, takiej jak int czy czy struct do metody przez referencję oraz zwracanie wartości z metody przez referencję to coś, co rzadko wykorzystuje się na co dzień, dlatego od czasu do czasu warto odświeżyć sobie tę wiedzę.

W załączonym kodzie możesz prześledzić różnice między przekazywaniem argumentów przez wartość oraz przez referencję. Przedstawiam przykłady przekazywania typów wartościowych oraz referencyjnych jako argumenty do metod i modyfikowania ich wartości wewnątrz metod.

Continue reading →
Prostota - Edsger Dijkstra

Prostota jest wielką zaletą

Prostota jest wielką zaletą, ale wymaga ciężkiej pracy, aby ją osiągnąć, i edukacji, aby ją docenić. Co gorsza, złożoność lepiej się sprzedaje.”

Edsger Dijkstra


Niedawno Boiling Frongs wypuścili nowe, fajne nagranie.

Sentencja jest zwięzła i gdyby się nad nią dłużej zastanowić, niesie za sobą olbrzymią ilość treści. Cała prelekcja jest bardzo dobra, a niżej linkuję do fragmentu który szczególnie mi się spodobał.

Boiling Frogs 2021 Jacek Lempart – Realizuj projekty oparte na pasji

If you’re good at the debugger it means you spent a lot of time debugging. I don’t want you to be good at the debugger.

Robert C. Martin

Adapter Obiektów

Pomysł na ten wpis jest taki, że na początek, napiszę testy jednostkowe, które będą palić się na czerwono, w których zdefiniuje problem. Testy zapalę na zielono poprzez implementację wzorca Adapter.

Adapter przekształca interfejs klas na inny, oczekiwany przez klienta. Adapter umożliwia współdziałanie klasom, które z uwagi na niezgodne interfejsy standardowo nie mogą współdziałać ze sobą.

„Wzorce Projektowe”, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides

Adapter dokonuje konwersji danej klasy do postaci innego interfejsu, zgodnie z oczekiwaniami klienta. Adapter pozwala na współpracę klas, które ze względu na niekompatybilne interfejsy wcześniej nie mogły ze sobą współpracować. {…}. Dzięki temu kod klienta nie musi być modyfikowany za każdym razem, kiedy będzie współpracować z innym interfejsem.

„Head First Design Patterns” Erich Freeman, Elisabeth Freeman, Bert Bates, Kathy Sierra

Adapter Obiektów

Na początek, napiszę test, w którym zdefiniuję problem. Po czym napiszę kod produkcyjny, który zapali ten test na zielono.

public class AdapterTester
{
  private readonly Client client;

  public AdapterTester()
  {
    client = new Client();
  }

  [Fact]
  public void call_request_method_in_target()
  {
    // arrange
    ITarget target = A.Fake();

    // act
    client.CallRequest(target);

    // assert
    A.CallTo(() => target.Request()).MustHaveHappened();
  }
}

Clinet posiada metodę CallRequest, która w argumencie pobiera obiekt spełniający interfejs ITarget. W teście sprawdzamy, czy w wyniku wywołania metody CallRequest została wywołana metoda Request na obiekcie target.

Aby ten test zapalił się na zielono potrzebna jest definicja interfejsu ITarget oraz implementacja klasy Client.

public interface ITarget
{
  void Request();
}

public class Client
{
  public void CallRequest(ITarget target)
  {
    target.Request();
  }
}

Teraz zdefiniuję kolejny test, którego zapalenie na zielono będzie wymagało napisania wzorca Adapter.

public class AdapterTester
{
  // ... pozostały kod jest niezmieniony
  [Fact]
  public void call_specific_request_method_in_adaptee()
  {
    // arrange
    IAdaptee adaptee = A.Fake();
    ITarget adapter = new Adapter(adaptee);

    // act
    client.CallRequest(adapter);

    // assert
    A.CallTo(() => adaptee.SpecificRequest()).MustHaveHappened();
  }
}

W teście tym pojawił się obiekt typu IAdaptee. IAdaptee posiada metodę SpecificRequest, co powoduje, że interfejs IAdaptee jest inny od ITarget. Klient nie będzie umieć współpracować z obiektem typu IAdapteeIAdaptee wymaga adaptacji do współdziałania z metodą CallRequest z klasy Client.

IAdaptee przekazany jest do konstruktora nowej klasy Adapter, która implementuje interfejs ITarget. Wewnątrz klasy Adapter nastąpi przetłumaczenie interfejsu IAdaptee na interfejs ITarget. W ostatniej linijce testu jest asercja mówiąca o tym, że w wyniku przetłumaczenia IAdaptee na ITarget, jeśli klient wywoła metodę Request na obiekcie typu ITarget to w rzeczywistości wywoła się metoda SpecificRequest na obiekcie typu IAdaptee.

Jak widać w teście, klasa Client pozostaje bez zmian — to ważne założenie. Adaptacja nowego interfejsu wykonywana jest bez najmniejszej zmiany klasy, do której ten nowy interfejs adaptujemy.

Test pali się teraz na czerwono. Kod się nie kompiluje. Definiuję teraz ten nowy interfejs, który będę adaptować.

public interface IAdaptee
{
  void SpecificRequest();
}

Teraz definiuję klasę Adapter, która w konstruktorze przyjmuje obiekt typu IAdaptee. Klasa Adapter implementuje interfejs ITarget, czyli ten pierwotny, do którego się adaptujemy.

public class Adapter : ITarget
{
  private readonly IAdaptee adaptee;

  public Adapter(IAdaptee adaptee)
  {
    this.adaptee = adaptee;
  }

  public void Request()
  {
    adaptee.SpecificRequest();
  }
}

Diagram klas wzorca Adapter Obiektów

Wzorzec Adapter Obiektów