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í.