Daleko častěji potřebuji volat cestu z prostředí vlastní Java aplikace.
Jedná se především o Camel cesty v roli žadatel. V případě komunikace typu zaslání zprávy nás nezajímá výsledek služby. Naopak v případě komunikace typu požadavek/odpověď nás výsledek zajímat bude. Rádi bychom jej dostali zpět do aplikace a nějak s ním dále pracovali.
V případě Camel cest, které plní roli poskytovatele, je tomu trochu jinak. Ty jsou iniciovány požadavkem, který přišel do jejich QUEUE nebo TOPIC. Tady jejich přímé volání z Java aplikace nepotřebujeme.
Do příkladů pro tento článek a navazující články jsem zařadil webové rozhraní. To budu dále používat pro vyvolávání služeb a případně předání výsledků (jak jsem dříve sliboval, nebudu pro startování služeb dále používat timer
).
Příklady k tomuto článku je možné najít v package: example03
Předávané zprávy
Všechny vydefinované typy zpráv jsou Java Bean, jejíž definice jsou v package entity. Vzhledem k tomu, že se od předchozích článků nezměnily, nebudu je tady dále rozebírat.
Definované Camel cesty
Jak již je zvykem, tak nejdříve jejich ukázka a pak komentáře k nim:
@Component public class CamelRoutes extends RouteBuilder { private static final Logger logger = LoggerFactory.getLogger(CamelRoutes.class); @Override public void configure() { // Applicant Route definitions ... from("direct:applicant01").routeId("applicant01") .to("activemq:queue:QUEUE-1"); from("direct:applicant02").routeId("applicant02") .multicast() .aggregationStrategy(new GroupedBodyAggregationStrategy()) .to("activemq:queue:QUEUE-1", "activemq:queue:QUEUE-2", "activemq:queue:QUEUE-3") .end(); // Provider Route definitions ... from("activemq:queue:QUEUE-1").routeId("provider01") .process(exchange -> { Request request = exchange.getMessage().getBody(Request.class); logger.info("... {}", request); exchange.getMessage().setBody(new Response("provider01", new Date(), request.getValue() + 10)); }); from("activemq:queue:QUEUE-2").routeId("provider02") .process(exchange -> { Request request = exchange.getMessage().getBody(Request.class); logger.info("... {}", request); exchange.getMessage().setBody(new Response("provider02", new Date(), (request.getValue() + 10) * 2)); }); from("activemq:queue:QUEUE-3").routeId("provider03") .process(exchange -> { Request request = exchange.getMessage().getBody(Request.class); logger.info("... {}", request); exchange.getMessage().setBody(new Response("provider03", new Date(), (request.getValue() + 50) * request.getValue())); }); } }
Cesty pro roli žadatel
Mám vydefinované dvě cesty, které budou plnit roli žadatele. Jedná se o cestu applicant01 a aplicant02. Ta první oslovuje poskytovatele přes frontu QUEUE-1, no a ta druhá pak dělá multicast na poskytovatele reprezentované frontami QUEUE-1, QUEUE-2 a QUEUE-3.
Oproti předchozím příkladům již na úrovni cesty nerozlišuji vzor komunikace (zdali se jedná o jednosměrnou nebo obousměrnou). To se udělá na úrovni volání cesty z aplikace.
Další změna je ve zdroji, odkud získává cesta zprávu. Jedná se o Camel komponentu direct.
Cesty pro roli poskytovatel
Tady se žádné překvapení nekoná. Mám vytvořené tři poskytovatele se svou frontou. Každý z nich příjme požadavek, zapíše jeho obsah do logu a vytvoří odpověď. Tu pak odešle zpět. A to je vše.
REST API aplikace
Tak to je nová věc, o které jsem se zatím nezmínil. V rámci SpringBoot je jako komponenta přidáno webové uživatelské rozhraní. Jako implementace je použita vložená knihovna Jetty.
Aplikační rozhraní se nastartuje společně se startem aplikace a běží na URL: http://localhost:8080
Dále je do aplikace doplněna komponenta implementující REST Controller. Najdete ji v package rest.
Opět nejdříve ukážu, a pak nějaké komentáře:
@RestController public class ServiceController { @Autowired private ProducerTemplate producerTemplate; @RequestMapping(value = "/mesg/appl01") public void sendApplicant01(@RequestParam(value = "value") long value) { Request request = new Request("mesg-applicant01", new Date(), value); producerTemplate.sendBody("direct:applicant01", request); } @RequestMapping(value = "/mesg/appl02") public void sendApplicant02(@RequestParam(value = "value") long value) { Request request = new Request("mesg-applicant02", new Date(), value); producerTemplate.sendBody("direct:applicant02", request); } @RequestMapping(value = "/call/appl01") public String callApplicant01(@RequestParam(value = "value") long value) { Request request = new Request("call-applicant01", new Date(), value); Response response = producerTemplate.requestBody("direct:applicant01", request, Response.class); return response.toString(); } @RequestMapping(value = "/call/appl02") public String callApplicant02(@RequestParam(value = "value") long value) { Request request = new Request("call-applicant02", new Date(), value); List<Response> responses = producerTemplate.requestBody("direct:applicant02", request, List.class); return responses.stream().map(Response::toString).collect(Collectors.joining("\n")); } @RequestMapping(value = "/rest/appl01") public ResponseEntity<Response> restApplicant01(@RequestBody Request request) { if (request.getName() == null) request.setName("rest-applicant01"); request.setTs(new Date()); Response response = producerTemplate.requestBody("direct:applicant01", request, Response.class); if (response != null) { return ResponseEntity.ok(response); } else { return ResponseEntity.notFound().build(); } } @RequestMapping(value = "/rest/appl02") public ResponseEntity<List<Response>> restApplicant02(@RequestBody Request request) { if (request.getName() == null) request.setName("rest-applicant02"); request.setTs(new Date()); List<Response> response = producerTemplate.requestBody("direct:applicant02", request, List.class); if (response != null) { return ResponseEntity.ok(response); } else { return ResponseEntity.notFound().build(); } } }
Mám vytvořeny tři typy REST služeb:
- začíná prefixem
/mesg
- jedná se o jednosměrnou komunikaci typu odeslání zpráv
- začíná prefixem
/call
- tady jde o obousměrnou komunikaci typu požadavek/odpověď
- začíná prefixem
/rest
- opět jde o obousměrnou komunikaci, ale rozhraní komunikuje formou JSON objektů
Abych se mohl napojit na cesty definované v Camel kontextu, potřebuji objekt implementující rozhraní ProducerTemplate. Pokud nemám nějaké speciální přání, mohu využít instanci vytvořenou v rámci SpringBoot (zajištěno anotací @Autowired).
Jak si to mohu vyzkoušet
Jednosměrná komunikace – odesílání zpráv
Hodnotu, kterou budu zadávat do požadavku, získám z parametru volání služby. Vytvořím požadavek, který předám do Camel cesty tímto voláním:
producerTemplate.sendBody("direct:applicant01", request);
V prvém parametru je URL identifikující cestu, ve druhém parametru pak bean předaný na vstup cesty.
Že se jedná o vzor jednosměrné komunikace, se určí použitou metodou, což je v tomto případě sendBody
.
Příklad volání první služby, kdy se zpráva předá jednomu příjemci:
[raska@localhost ~]$ curl -s http://localhost:8080/mesg/appl01?value=11
A tohle by se mělo objevit v logu jako záznam o přijetí zprávy od poskytovatele:
2020-12-13 18:34:36.815 INFO: ... Request{value=11, Token{name='mesg-applicant01', ts=Sun Dec 13 18:34:36 CET 2020}}
Jako další příklad je odeslání zprávy více příjemcům:
[raska@localhost ~]$ curl -s http://localhost:8080/mesg/appl02?value=22
A takto by se to mělo projevit v logu:
2020-12-13 18:39:47.479 INFO: ... Request{value=22, Token{name='mesg-applicant02', ts=Sun Dec 13 18:39:47 CET 2020}} 2020-12-13 18:39:47.480 INFO: ... Request{value=22, Token{name='mesg-applicant02', ts=Sun Dec 13 18:39:47 CET 2020}} 2020-12-13 18:39:47.500 INFO: ... Request{value=22, Token{name='mesg-applicant02', ts=Sun Dec 13 18:39:47 CET 2020}}
Obousměrná komunikace – požadavek/odpověď
Způsob získání hodnoty a vytvoření požadavku je stejný jako v předchozích příkladech. Nicméně v případě obousměrné komunikace cestu vyvolám takto:
Response response = producerTemplate.requestBody("direct:applicant01", request, Response.class);
V prvém parametru je URL identifikující cestu, ve druhém parametru bean předaný na vstup cesty, a ve třetím je třída, jakou by měl mít očekávaný výsledek. Ten pak předám jako výsledek volání webové služby.
Že se jedná o vzor obousměrné komunikace, se určí použitou metodou, což je v tomto případě requestBody
.
Jako první příklad může posloužit:
[raska@localhost ~]$ curl -s http://localhost:8080/call/appl01?value=11 Response{result=21, Token{name='provider01', ts=Sun Dec 13 18:45:21 CET 2020}}
Vrátila se mně jedna odpověď od poskytovatele provider01.
Nebo druhý příklad, kdy oslovím více poskytovatelů:
[raska@localhost ~]$ curl -s http://localhost:8080/call/appl02?value=22 Response{result=32, Token{name='provider01', ts=Sun Dec 13 18:45:29 CET 2020}} Response{result=64, Token{name='provider02', ts=Sun Dec 13 18:45:29 CET 2020}} Response{result=1584, Token{name='provider03', ts=Sun Dec 13 18:45:29 CET 2020}}
No a můžete si ještě zkontrolovat obsah logů. Opět by tam měly být záznamy o přijatých požadavcích.
Obousměrná komunikace jako REST služba
Jedná se o variaci na obousměrnou komunikaci. Rozdíl není ve způsobů interakce s Camel cestami, ale ve způsobu předávání parametrů a zobrazení výsledků.
Parametry pro vyvolání služby zde předávám v těle HTTP dotazu jako JSON objekt. Výsledek služby je pak předán v těle HTTP odpovědi, a to opět jako JSON objekt.
Takže další příklady bez dalších zbytečných komentářů.
Volání jednoho poskytovatele:
[raska@localhost ~]$ curl -s -d '{ "value":"11", "name": "REQUESTED by TRPASLIK" }' -H 'Content-Type: application/json' http://localhost:8080/rest/appl01 | jq . { "name": "provider01", "ts": "2020-12-13T17:51:48.022+00:00", "result": 21 }
Volání více poskytovatelů:
[raska@localhost ~]$ curl -s -d '{ "value": "22", "name": "REQUESTED by TRPASLIK" }' -H 'Content-Type: application/json' http://localhost:8080/rest/appl02 | jq . [ { "name": "provider01", "ts": "2020-12-13T17:52:07.547+00:00", "result": 32 }, { "name": "provider02", "ts": "2020-12-13T17:52:07.566+00:00", "result": 64 }, { "name": "provider03", "ts": "2020-12-13T17:52:07.606+00:00", "result": 1584 } ]
V dalším pokračování tohoto seriálu budu používat pro vyvolání služeb již pouze REST rozhraní.