Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond REST and CRUD - Integration patterns in ...

Beyond REST and CRUD - Integration patterns in Microservices

The flow of information and events through distributed systems goes well beyond HTTP, REST, and JSON. In this tech talk, we’ll use Apache Camel to explore integration patterns that add flexibility and resilience to your evolving architecture.

Zineb Bendhiba

October 10, 2024
Tweet

More Decks by Zineb Bendhiba

Other Decks in Programming

Transcript

  1. Beyond REST and CRUD Integration patterns in Microservices ➕ Erin

    Schnabel, Red Hat ・ ・ Zineb Bendhiba, Red Hat ・ ・ github.com/ebullient www.ebullient.dev github.com/zbendhiba zinebbendhiba.com 1
  2. How do we adapt? Applications must evolve Rarely ever a

    clean slate Decouple elements of the system 3
  3. Term Meaning Message data transferred by a route Exchange envelope;

    wraps the data Endpoint a channel; receiver or sender Component know-how; creates endpoints Processor Java API; custom logic Camel message routing... 8
  4. REST and REST... @Path("/pockets") public class TheoreticalPockets { @GET public

    Response getProfiles() { // fetch some stuff } @GET @Path("{profile}") public Response getProfile(@PathParam("profile") String id) { // fetch specific stuff } @POST @Path("{profile}") public Response updateProfile(@PathParam("profile") String id, ...) { // do important things } } 11
  5. Flow of data with Camel Route: REST rest("/pockets") .post("/{profile}") .to("direct:validateAndModifyPockets");

    // <--- HERE 1 .get() 2 .to("direct:getProfiles") 3 4 .get("/{profile}") 5 .to("direct:getProfile") 6 7 8 9 13
  6. Flow of data with Camel Route: REST @Transactional public void

    validateProfile(Exchange exchange) { Message message = exchange.getMessage(); String profile = (String) message.getHeader(profileKey); profile = ProfileContext.validateProfile(profile); message.setHeader(profileKey, profile); 1 2 final String profileKey = "profile"; 3 4 5 6 7 // Update header with full name, may throw 8 9 10 11 } 12 13
  7. Flow of data with Camel Route: REST .bean(webRouteHandler, "doModification(`${body}, $`{header.profile})")

    @Transactional public ModificationResponse doModification( ModificationRequest modificationRequest, String profileName) { profileContext.setActiveProfile(profileName); return modifyPockets.modifyPockets(modificationRequest); 1 2 ~~~~~~ 3 4 5 6 7 8 9 10 11 12 } 13 13
  8. Flow of data with Camel Route: CLI @ActivateRequestContext @Transactional String

    activeProfile = activeProfileMixin.findActiveProfileId(); profileContext.setActiveProfile(activeProfile); return delegateCall(); // delegate to sub-command @Override 1 2 3 public final Integer call() { 4 try { 5 // Validate specified profile 6 7 8 if (activeProfile == null) { 9 return ExitCode.USAGE; 10 } 11 12 13 if (showTypes) { 14 showTypes(); 15 return ExitCode.OK; 16 } 17 18 19 } catch (...) { 20 ... 21 } 22 } 23 14
  9. Flow of data with Camel Route: CLI public Integer delegateCall()

    { Modification modification = createModification(); ModificationRequest req = createModificationRequest( "Create " + modification.itemDetails.name) ModificationResponse response = modifyPocketRoute.modifyPockets(req); @Override 1 2 3 4 5 6 7 .add(modification); 8 9 // Make the modification 10 11 12 response.changes.stream().filter(c -> c.type == PostingType.CREATE) 13 .forEach(c -> { 14 ... 15 }); 16 17 return ExitCode.OK; 18 } 19 15
  10. Flow of data with Camel Route: CLI // Make the

    modification ModificationResponse response = modifyPocketRoute.modifyPockets(req); public interface CliModifyPocket { ModificationResponse modifyPockets(ModificationRequest request); } @Produce("direct:modifyPockets") CliModifyPocket modifyPocketRoute; 1 2 3 ~~~~ 4 5 6 7 8 9 10 11 16
  11. from("direct:modifyPockets") .to("log:dev.ebullient.pockets?level=DEBUG") .bean(modifyPockets, "modifyPockets(${body})") .process((e) -> emitHandler.setProfile(e)) .wireTap("direct:emitRoute") .to("log:dev.ebullient.pockets?level=DEBUG"); 1

    .errorHandler(noErrorHandler()) 2 3 4 5 6 7 8 9 10 11 12 Flow of data with Camel Route: CLI public void setProfile(Exchange e) { ModificationResponse resp = msg.getBody(ModificationResponse.class); msg.setHeader("profile", resp.profileName); 1 Message msg = e.getMessage(); 2 3 4 5 6 } 7 17
  12. Common path: WireTap What does this do? .wireTap("direct:emitRoute") It kicks

    off a new route to transform content from("direct:emitRoute") .bean(emitHandler, "emitRoute") 1 .to("log:dev.ebullient.pockets?level=DEBUG") 2 3 .recipientList(header("routes")) 4 .parallelProcessing(); 5 18
  13. Common path: Recipient List public void emitRoute(Exchange exchange) { String

    profileName = msg.getHeader("profile", "default", String.class); List<String> emitters = new ArrayList<>(); if (localPocketsConfig.jsonEventLogEnabled(profileName)) { emitters.add("direct:jsonEmitter"); if (localPocketsConfig.markdownEnabled(profileName)) { emitters.add("direct:markdownEmitter"); msg.setHeader("routes", String.join(",", emitters)); @SuppressWarnings("unused") 1 2 Message msg = exchange.getMessage(); 3 4 5 6 7 EventLogConfiguration eventLogConfig = 8 localPocketsConfig.eventLogConfig(profileName); 9 msg.setHeader("jsonEventLogDirectory", 10 eventLogConfig.getDirectory().toString()); 11 12 } 13 14 15 } 16 17 } 18 19
  14. Common path: direct:jsonEmitter from("direct:jsonEmitter") .setHeader(FileConstants.FILE_NAME, simple("${header.profile}.events.jsonl")) .setBody((e) -> Transform.toJsonString(e.getMessage().getBody())) .toD("file:${header.jsonEventLogDirectory}?"

    + "fileExist=Append&appendChars=\n&allowNullBody=true") 1 .onException(InvalidPocketState.class) 2 .handled(true) 3 .process(new HandleInvalidPocketState()) 4 .end() 5 6 7 8 9 10 11 12 13 14 .to("log:dev.ebullient.pockets?level=DEBUG"); 15 21
  15. Common path: Qute templates from("direct:markdownFile") .to("qute:dummy?allowTemplateFromHeader=true") .toD("file:${header.markdownDirectory}?" + "fileExist=Override&allowNullBody=true") 1

    .onException(InvalidPocketState.class) 2 .handled(true) 3 .process(new EmitRouteHandler.HandleInvalidPocketState()) 4 .end() 5 6 7 8 9 10 11 .to("log:dev.ebullient.pockets?level=DEBUG"); 12 23
  16. Summary Apache Camel can help fill in the gaps Use

    it alongside your favorite messaging solution (Kafka or traditional message brokers) 25
  17. Summary Apache Camel can help fill in the gaps Use

    it alongside your favorite messaging solution (Kafka or traditional message brokers) It applies to patterns far beyond REST and CRUD 25
  18. Summary Apache Camel can help fill in the gaps Use

    it alongside your favorite messaging solution (Kafka or traditional message brokers) It applies to patterns far beyond REST and CRUD Use it with your favorite Java framework 25
  19. Summary Apache Camel can help fill in the gaps Use

    it alongside your favorite messaging solution (Kafka or traditional message brokers) It applies to patterns far beyond REST and CRUD Use it with your favorite Java framework Use it with JBang for script nonsense 25
  20. Summary Apache Camel can help fill in the gaps Use

    it alongside your favorite messaging solution (Kafka or traditional message brokers) It applies to patterns far beyond REST and CRUD Use it with your favorite Java framework Use it with JBang for script nonsense Use it with Kubernetes to make the best glue (Camel-K, Kamelets) 25