{"id":31722,"date":"2020-10-21T08:26:45","date_gmt":"2020-10-21T06:26:45","guid":{"rendered":"https:\/\/nearshore-it.eu\/artykuly\/wsparcie-dla-spring-webflux-we-frameworku-pact-jvm\/"},"modified":"2024-09-17T17:19:17","modified_gmt":"2024-09-17T15:19:17","slug":"wsparcie-dla-spring-webflux-we-frameworku-pact-jvm","status":"publish","type":"post","link":"https:\/\/nearshore-it.eu\/pl\/artykuly\/wsparcie-dla-spring-webflux-we-frameworku-pact-jvm\/","title":{"rendered":"Wsparcie dla Spring WebFlux we frameworku Pact JVM"},"content":{"rendered":"\n<div class=\"table-of-contents\">\n    <p class=\"title\">Przejd\u017a do:<\/p>\n    <ol>\n                    <li><a href=\"#Czym-jest-Pact-Consumer-Driven-Contract-Testing\">1.  Czym jest Pact Consumer Driven Contract Testing<\/a><\/li>\n                    <li><a href=\"#Czym-jest-Spring-WebFlux\">2.  Czym jest Spring WebFlux<\/a><\/li>\n                    <li><a href=\"#Pact-i-WebFlux-jak-to-dziala-razem\">3.  Pact i WebFlux \u2013 jak to dzia\u0142a razem<\/a><\/li>\n                    <li><a href=\"#Podsumowanie\">4.  Podsumowanie<\/a><\/li>\n                    <li><a href=\"#Zrodla\">5.  \u0179r\u00f3d\u0142a<\/a><\/li>\n            <\/ol>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"Czym-jest-Pact-Consumer-Driven-Contract-Testing\">Czym jest Pact Consumer Driven Contract Testing<\/h2>\n\n\n\n<p>Consumer Driven Contract Testing to wzorzec wykorzystywany w testowaniu kompatybilno\u015bci pomi\u0119dzy konsumentami (consumer) i dostawcami (provider) serwis\u00f3w. Og\u00f3lna idea jest taka, \u017ce pomi\u0119dzy tymi dwoma systemami istnieje kontrakt opisuj\u0105cy zachodz\u0105ce mi\u0119dzy nimi interakcje. Je\u017celi obydwa systemy wype\u0142niaj\u0105 jego za\u0142o\u017cenia, test ko\u0144czy si\u0119 powodzeniem. Podej\u015bcie \u201eConsumer driven\u201d oznacza, \u017ce si\u0142\u0105 nap\u0119dow\u0105 ewolucji kontraktu s\u0105 konsumenci serwisu, a nie dostawca.<\/p>\n\n\n\n<p>Wsparciem dla Consumer Contract Testing jest zestaw framework\u00f3w <a href=\"https:\/\/pact.io\" target=\"_blank\" rel=\"noopener\">DiUS Pact<\/a><u>.<\/u> Frameworki te koncentruj\u0105 si\u0119 g\u0142\u00f3wnie na komunikacji HTTP, cho\u0107 dla niekt\u00f3rych platform dost\u0119pne jest r\u00f3wnie\u017c wsparcie dla kolejek. Obecnie implementacje pokrywaj\u0105 wi\u0119kszo\u015b\u0107 popularnych \u015brodowisk uruchomieniowych, takich jak JVM, .NET, JavaScript czy Ruby.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Czym-jest-Spring-WebFlux\">Czym jest Spring WebFlux<\/h2>\n\n\n\n<p>Spring WebFlux to reaktywny framework webowy wprowadzony w Springu 5. Jest to odpowiednik dobrze znanego Spring MVC, z t\u0105 r\u00f3\u017cnic\u0105, \u017ce opiera si\u0119 na nieblokuj\u0105cym API dostarczanym przez <a href=\"https:\/\/www.reactive-streams.org\/\" target=\"_blank\" rel=\"noopener\">reactive streams<\/a>. WebFlux jest w stanie obs\u0142ugiwa\u0107 du\u017ce obci\u0105\u017cenia na ma\u0142ej liczbie w\u0105tk\u00f3w, co skutkuje mniejszym zu\u017cyciem zasob\u00f3w sprz\u0119towych.<\/p>\n\n\n\n<p>WebFlux oferuje dwa modele programowania:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>adnotowane kontrolery<\/li>\n\n\n\n<li>endpointy funkcyjne<\/li>\n<\/ul>\n\n\n\n<p>Pact obs\u0142uguje oba modele, natomiast dla potrzeb tego artyku\u0142u skupi\u0119 si\u0119 na endpointach funkcyjnych.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Pact-i-WebFlux-jak-to-dziala-razem\">Pact i WebFlux \u2013 jak to dzia\u0142a razem<\/h2>\n\n\n\n<p>Poniewa\u017c to na kliencie spoczywa odpowiedzialno\u015b\u0107 za dostarczanie kontraktu, testy Pact pisane s\u0105 najpierw dla niego. Ka\u017cdy z test\u00f3w zawiera opis interakcji pomi\u0119dzy klientem i dostawc\u0105, np. requesty i spodziewane odpowiedzi HTTP. Maj\u0105c te dane, pact framework uruchomi tymczasowy serwer HTTP zwracaj\u0105cy predefiniowane odpowiedzi i wykona testy klienta.<\/p>\n\n\n\n<p>Niejako efektem ubocznym wykonania testu jest utworzenie pliku kontraktu. Ten sam plik zostanie p\u00f3\u017aniej u\u017cyty do sprawdzenia, czy dostawca serwisu spe\u0142nia za\u0142o\u017cenia kontraktu. Po stronie serwera Pact wykona testy, u\u017cywaj\u0105c request\u00f3w zapisanych w kontrakcie i zweryfikuje poprawno\u015b\u0107 odpowiedzi. Mo\u017cemy tutaj dokona\u0107 wyboru, czy chcemy przetestowa\u0107 serwis jako ca\u0142o\u015b\u0107, czy tylko jego wycinek odpowiedzialny za komunikacj\u0119 HTTP. W tym artykule opisz\u0119 ten drugi przypadek.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Test dla klienta<\/h3>\n\n\n\n<p>Za\u0142\u00f3\u017cmy, \u017ce dostawca udost\u0119pnia endpoint HTTP. Odpowiada on na \u017c\u0105dania HTTP <strong><em>GET<\/em><\/strong><em>,<\/em> zwracaj\u0105c <em>JSONa <\/em>reprezentuj\u0105cego kolekcj\u0119 obiekt\u00f3w <strong><em>Foo<\/em><\/strong>. U\u017cyjmy frameworka <a href=\"https:\/\/github.com\/spockframework\/spock\" target=\"_blank\" rel=\"noopener\">Spock<\/a> do zakodowania pact-testu klienta.<\/p>\n\n\n\n<p>Najpierw zdefiniujmy pakt w sekcji <strong><em>given<\/em><\/strong> naszego testu. Wykorzystajmy do tego celu klas\u0119 <strong><em>ConsumerPactBuilder,<\/em><\/strong> kt\u00f3r\u0105 udost\u0119pnia biblioteka Pact:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">given:\ndef pact = ConsumerPactBuilder.consumer(\"consumerService\")\n    .hasPactWith(\"providerService\")\n    .uponReceiving(\"sample request\")\n    .method(\"GET\")\n    .path(\"\/foo\")\n    .willRespondWith()\n    .status(200)\n    .headers([\"Content-Type\": \"application\/json\"])\n    .body(\"\"\"\n            [\n                {\"id\": 1, \"name\": \"Foo\"},\n                {\"id\": 2, \"name\": \"Bar\"}\n            ]\n        \"\"\".stripIndent())\n    .toPact()\n<\/pre>\n\n\n\n<p>Widzimy tutaj, \u017ce &nbsp;kontakt definiuje interakcje nazwan\u0105 <strong><em>sample request<\/em><\/strong> pomi\u0119dzy klientem o nazwie <strong><em>consumerService<\/em><\/strong> i dostawc\u0105 <strong><em>providerService.<\/em><\/strong> W reakcji na \u017c\u0105danie b\u0119d\u0105ce wywo\u0142aniem metody HTTP <em>GET<\/em> na \u015bcie\u017cce <em>\/foo<\/em>, dostawca powinien odpowiedzie\u0107 statusem <em>HTTP 200,<\/em> a w sekcji <strong><em>body<\/em><\/strong> odpowiedzi powinny by\u0107 dostarczone reprezentacje dw\u00f3ch obiekt\u00f3w <strong><em>Foo<\/em>.<\/strong><\/p>\n\n\n\n<p>Zakodujmy teraz cz\u0119\u015b\u0107 z asercjami oraz wywo\u0142aniem naszego klienta w sekcji <strong><em>when<\/em>:<\/strong><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">when:\ndef result = ConsumerPactRunnerKt.runConsumerTest(\n    pact, MockProviderConfig.createDefault()) { mockServer, context ->\n\n    def webClient = WebClient.create(mockServer.getUrl())\n    def consumerAdapter = new ConsumerAdapter(webClient)\n\n    def resultFlux = consumerAdapter.invokeProvider()\n\n    StepVerifier.create(resultFlux)\n        .expectNext(new Foo(1l, 'Foo'))\n        .expectNext(new Foo(2l, 'Bar'))\n        .verifyComplete()\n}\n<\/pre>\n\n\n\n<p>Do wykonania testu klienta u\u017cyjemy klasy <strong><em>ConsumerPactRunnerKt<\/em><\/strong> dostarczanej przez bibliotek\u0119 Pact. Metoda <strong><em>runConsumerTest<\/em>,<\/strong> przed wykonaniem kodu z domkni\u0119cia, uruchomi tymczasowy serwer, kt\u00f3ry b\u0119dzie odpowiada\u0107 na \u017c\u0105dania HTTP zdefiniowane wcze\u015bniej w pakcie. Zapisze r\u00f3wnie\u017c kontrakt w postaci pliku JSON, kt\u00f3ry b\u0119dzie wsp\u00f3\u0142dzielony z dostawc\u0105 us\u0142ugi. Ostatnim z parametr\u00f3w tej metody b\u0119dzie blok kodu zawieraj\u0105cy wywo\u0142anie napisanego przez nas klienta serwisu. W tym przypadku utworzymy reaktywnego <strong><em>webClienta<\/em><\/strong> i przeka\u017cemy mu URL utworzonego przez Pact serwera HTTP. Nast\u0119pnie stworzymy instancj\u0119 naszego adaptera <strong>(<em>ConsumerAdapter<\/em>),<\/strong> na kt\u00f3rej wywo\u0142amy metod\u0119 <strong><em>invokeProvider()<\/em><\/strong> odpowiedzialn\u0105 za interakcj\u0119 HTTP z dostawc\u0105. Poniewa\u017c wynikiem tej interakcji b\u0119dzie <em>Flux<\/em> do zbadania jej poprawno\u015bci, mo\u017cemy u\u017cy\u0107 <strong><em>StepVerifiera<\/em><\/strong> z projektu Reactor.<\/p>\n\n\n\n<p>Ostatni\u0105 faz\u0105 b\u0119dzie sprawdzenie w sekcji <strong><em>then,<\/em><\/strong> czy weryfikacja kontraktu si\u0119 powiod\u0142a:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">then:\nresult instanceof PactVerificationResult.Ok<\/pre>\n\n\n\n<p>To w\u0142a\u015bnie tutaj mo\u017cemy sprawdzi\u0107, czy <strong><em>StepVerifier<\/em> <\/strong>nie wygenerowa\u0142 wyj\u0105tku (b\u0119dzie on owini\u0119ty w klasie <strong><em>PactVerificationResult.Error<\/em>)<\/strong> lub jaka\u015b opisana w kontakcie interakcja nie zosta\u0142a wywo\u0142ana lub by\u0142a z nim niezgodna.<\/p>\n\n\n\n<p>Napiszmy teraz kod przyk\u0142adowego klienta wykorzystywanego w te\u015bcie. B\u0119dzie to standardowy przypadek u\u017cycia reaktywnego <strong><em>webClienta<\/em> <\/strong>do odczytu danych z endpointu <em>\/foo<\/em>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public Flux&lt;Foo> invokeProvider() { \n    return webClient \n            .get() \n            .uri(\"\/foo\") \n            .accept(MediaType.APPLICATION_JSON) \n            .retrieve() \n            .bodyToFlux(Foo.class); \n}<\/pre>\n\n\n\n<p>Jeste\u015bmy teraz gotowi do wykonania testu. Uruchomi on dostarczany przez framework serwer HTTP \u2013 wywo\u0142a go, u\u017cywaj\u0105c nale\u017c\u0105cej do klienta klasy adaptera, a nast\u0119pnie zweryfikuje odpowiedzi. Dodatkowo Pact utworzy w katalogu <strong><em>build\/pacts<\/em><\/strong> plik JSON b\u0119d\u0105cy reprezentacj\u0105 paktu. Plik ten nale\u017cy nast\u0119pnie udost\u0119pni\u0107 testom kontraktu providera serwisu.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Test dla dostawcy<\/h3>\n\n\n\n<p>Poniewa\u017c mamy ju\u017c gotowy plik kontraktu, test dla providera serwisu b\u0119dzie nieco bardziej zwi\u0119z\u0142y. Tym razem u\u017cyjemy frameworku <a href=\"https:\/\/junit.org\" target=\"_blank\" rel=\"noopener\">JUnit<\/a><u>,<\/u> poniewa\u017c Spockowy wzorzec <em>given &#8211; when &#8211; then<\/em> nie by\u0142by tutaj zbyt pomocny:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@RunWith(RestPactRunner.class) \n@Provider(\"providerService\") \n@PactFolder(\"pacts\") \npublic class ProviderRouterPactTest { \n \n    @TestTarget \n    public WebFluxTarget target = new WebFluxTarget(); \n \n    private ProviderHandler handler = new ProviderHandler(); \n    private RouterFunction&lt;ServerResponse> routerFunction \n            = new ProviderRouter(handler).routes(); \n \n    @Before \n    public void setup() { \n        target.setRouterFunction(routerFunction); \n    } \n}<\/pre>\n\n\n\n<p>Jak mo\u017cemy zauwa\u017cy\u0107, kod nie zawiera \u017cadnych metod testowych. Dzieje si\u0119 tak dlatego, \u017ce testowane wywo\u0142ania i asercje s\u0105 ju\u017c obecne w pliku kontraktu. <strong><em>RestPactRunner<\/em> <\/strong>(wskazany w adnotacji <strong><em>@RunWith<\/em><\/strong>) u\u017cyje tego pliku i zajmie si\u0119 wykonaniem przypadk\u00f3w testowych w nim opisanych oraz weryfikacj\u0105 odpowiedzi. Musimy jeszcze poinformowa\u0107 runnera o lokalizacji kontrakt\u00f3w (w tym przypadku poprzez adnotacj\u0119 <strong><em>@PactFolder<\/em><\/strong> wskazujemy katalog na dysku) i nazwie dostawcy (za pomoc\u0105 adnotacji <strong><em>@Provider<\/em><\/strong>). Nazwa ta jest o tyle istotna, \u017ce silnik Pactu b\u0119dzie wybiera\u0107 kontrakty do przetestowania, por\u00f3wnuj\u0105c j\u0105 z nazw\u0105 podan\u0105 w te\u015bcie klienta jako <strong><em>.hasPactWith(&#8222;providerService&#8221;). <\/em><\/strong>Adnotacja <strong><em>@TestTarget<\/em><\/strong> jest odpowiedzialna za wskazanie celu do testowania. Dla endpoint\u00f3w WebFluxowych b\u0119dzie to instancja <strong><em>WebFluxTarget<\/em>.<\/strong> Nale\u017cy w niej ustawi\u0107 ju\u017c bezpo\u015brednio <strong><em>routerFunction,<\/em><\/strong> kt\u00f3rej u\u017cywamy w kodzie dostawcy. Wygodnym miejscem do zrobienia tego jest tutaj metoda <strong><em>setup()<\/em><\/strong> testu.<\/p>\n\n\n\n<p>Nale\u017cy jeszcze wspomnie\u0107 o klasach <strong><em>ProviderHandler<\/em> <\/strong>oraz <strong><em>ProviderRouter<\/em>.<\/strong> S\u0105 to elementy implementacji przyk\u0142adowego dostawcy. Router jest odpowiedzialny za budowanie instancji <strong><em>RouterFunction,<\/em><\/strong> kt\u00f3re wi\u0105\u017c\u0105 \u015bcie\u017cki URL z kodem handlera:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@Configuration \n@RequiredArgsConstructor \n@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) \nclass ProviderRouter { \n \n    ProviderHandler handler; \n \n    @Bean \n    RouterFunction&lt;ServerResponse> routes() { \n        return route() \n                .GET(\"\/foo\", accept(APPLICATION_JSON), handler::getFoo) \n                .build(); \n    } \n \n}<\/pre>\n\n\n\n<p>Natomiast metoda handler::getFoo z powy\u017cszego przyk\u0142adu jest odpowiedzialna za zwracanie Mono zawieraj\u0105cego odpowied\u017a dostawcy:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Mono&lt;ServerResponse> getFoo(ServerRequest request) { \n    return ServerResponse \n            .ok() \n            .contentType(APPLICATION_JSON) \n            .body(Flux.just( \n                    new Foo(1l, \"Foo\"), \n                    new Foo(2l, \"Bar\") \n            ), Foo.class); \n}<\/pre>\n\n\n\n<p>Handler zwraca tutaj dwa obiekty <em>Foo<\/em> tak jak jest to okre\u015blone w kontakcie mi\u0119dzy dostawc\u0105 a klientem.<\/p>\n\n\n\n<p>Uruchomienie testu dla dostawcy b\u0119dzie teraz skutkowa\u0107 wczytaniem pliku kontraktu, dopasowaniem odpowiedniego dla \u015bcie\u017cki HTTP handlera, wywo\u0142aniem go i weryfikacj\u0105, czy odpowied\u017a systemu jest zgodna z t\u0105 okre\u015blon\u0105 w kontrakcie.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Podsumowanie\">Podsumowanie<\/h2>\n\n\n\n<p>Pact Consumer Driven Contracts to bardzo przydatne narz\u0119dzie do zapewniania sp\u00f3jno\u015bci pomi\u0119dzy elementami z\u0142o\u017conego systemu. Z pewno\u015bci\u0105 jego zalet\u0105 jest szeroki wachlarz wspieranych technologii, kt\u00f3ry pozwala na testowanie oparte na kontraktach w heterogenicznym \u015brodowisku. Teraz do tego spektrum do\u0142\u0105czy\u0142 kolejny element \u2013 technologia Spring WebFlux, dzi\u0119ki czemu zyskali\u015bmy mo\u017cliwo\u015b\u0107 wykonywania test\u00f3w wzgl\u0119dem reaktywnych dostawc\u00f3w.<\/p>\n\n\n\n<p>Kod \u017ar\u00f3d\u0142owy przyk\u0142ad\u00f3w u\u017cytych w tym artykule jest dost\u0119pny w repozytorium <a href=\"https:\/\/github.com\/paweusz\/pactwebflux\" target=\"_blank\" rel=\"noopener\">Githuba<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"Zrodla\">\u0179r\u00f3d\u0142a<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Strona <a href=\"https:\/\/github.com\/DiUS\/pact-jvm\" target=\"_blank\" rel=\"noopener\">Github<\/a> Pact JVM<\/li>\n\n\n\n<li><a href=\"https:\/\/docs.spring.io\/spring\/docs\/current\/spring-framework-reference\/web-reactive.html\" target=\"_blank\" rel=\"noopener\">Dokumentacja<\/a> Spring WebFlux<\/li>\n\n\n\n<li><a href=\"https:\/\/martinfowler.com\/articles\/consumerDrivenContracts.html\" target=\"_blank\" rel=\"noopener\">\u201eConsumer-Driven Contracts: A Service Evolution Pattern&#8221;<\/a><u>,<\/u> Ian Robinson<\/li>\n\n\n\n<li>Przyk\u0142ad integracji Pact \u2013 WebFlux na <a href=\"https:\/\/github.com\/paweusz\/pactwebflux\" target=\"_blank\" rel=\"noopener\">Githubie<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Pact JVM posiada wsparcie dla Springa od do\u015b\u0107 dawna, ale po stronie dostawcy us\u0142ugi by\u0142o ono niestety ograniczone tylko do mockowanego Spring MVC. Pocz\u0105wszy od wersji 4.7.1, Pact wspiera r\u00f3wnie\u017c endpointy Spring WebFlux. W tym artykule zademonstruj\u0119 u\u017cycie Pact Consumer Driven Contracts do testowania serwis\u00f3w wykonanych za pomoc\u0105 Spring WebFlux oraz konsument\u00f3w tych us\u0142ug. Zaczn\u0119 od kr\u00f3tkiego wprowadzenia do Consumer Driven Contract Testing i Spring WebFlux. Nast\u0119pnie wyja\u015bni\u0119, jak po\u0142\u0105czy\u0107 te dwie technologie, aby utworzy\u0107 kontrakt i zweryfikowa\u0107 za jego pomoc\u0105 zar\u00f3wno konsumenta (consumer), jak i dostawc\u0119 (provider) us\u0142ugi.<\/p>\n","protected":false},"author":180,"featured_media":31723,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"iawp_total_views":11,"footnotes":""},"categories":[1,582],"tags":[568,562],"offering":[522],"class_list":["post-31722","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-artykuly","category-technologie","tag-java-pl","tag-qa","offering-tech-blog"],"acf":[],"_links":{"self":[{"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/posts\/31722","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/users\/180"}],"replies":[{"embeddable":true,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/comments?post=31722"}],"version-history":[{"count":2,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/posts\/31722\/revisions"}],"predecessor-version":[{"id":32459,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/posts\/31722\/revisions\/32459"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/media\/31723"}],"wp:attachment":[{"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/media?parent=31722"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/categories?post=31722"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/tags?post=31722"},{"taxonomy":"offering","embeddable":true,"href":"https:\/\/nearshore-it.eu\/pl\/wp-json\/wp\/v2\/offering?post=31722"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}