Content resolution example with Spring Cloud Stream

dakotahnorth
dakotahnorth Member Posts: 8 ✭✭
edited April 15 in PubSub+ Event Broker #1

The Spring documentation mentions one of the core features of the framework:

It tries to automatically convert incoming message payloads to type 

Person

with the following code:


@Bean public Consumer<Person> log() {
return person -> {
System.out.println("Received: " + person);
};
}


My attempts to have the incoming message automatically convert into a Person type with Solace have failed.

Please point me to an Solace example of sending & receiving messages thru Spring Cloud Stream where the incoming message payload is automatically converted.

Best Answer

  • Aaron
    Aaron Member, Administrator, Moderator, Employee Posts: 638 admin
    #2 Answer ✓

    I cloned your code, even installed a JDK21. Got it working by moving the Person event class out of the Application class and into it's own class.

    Application.java

    @SpringBootApplication
    public class HelloWorldSpringCloudStreamApplication {
    
        public static void main(String... args) {
            SpringApplication.run(HelloWorldSpringCloudStreamApplication.class, args);
    
        }
    
        @Bean
        public Consumer<PersonEvent> exampleEventConsumer() {
    
            return personEvent -> {
                System.out.println("Received event: " + personEvent.getName());
    
            };
        }
    }
    

    PersonEvent.java

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class PersonEvent {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "PersonEvent, name="+name;
        }
    }
    

    Also, you were escaping your quotes in your payload..? Here's a handy command-line tool my colleague wrote that is useful for things like this: https://github.com/SolaceLabs/solace-tryme-cli Just download the latest release, that's what I use:

    $ ./stm send -t try-me -m '{"name":"Aaron"}'
    

    Hope that's enough to get you going now..! 👍🏼

Answers

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭
    edited April 15 #3

    Spring Cloud Stream documentation … https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream.html#spring-cloud-stream-preface-adding-message-handler

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭

    This is also referenced in the Solace code labs …

    By default when coding your Spring Cloud Stream microservice you are writing Spring Cloud Function beans that can be re-used for multiple purposes and can leverage the framework's 

    Content Type Negotiation

     to pass your POJOs directly into the function while decoupling your business logic from the specific runtime target and triggering mechanism (web endpoint, stream processor, task). 

    https://codelabs.solace.dev/codelabs/spring-cloud-stream-beyond/?_gl=1*155nf2j*_ga*MTMyOTkzNDg4OC4xNzExOTM3MDcw*_ga_XZ3NWMM83E*MTcxMzEwMTgxOC4xMy4wLjE3MTMxMDE4MTguMC4wLjA.#3

  • Aaron
    Aaron Member, Administrator, Moderator, Employee Posts: 638 admin

    Hi @dakotahnorth, welcome to the Community. You're going to need some way to deserialize the payload into your POJO. What format is your payload in? Spring can't just guess how to do this.

    For example, if you look at the code generated by the Spring Cloud Stream generator for AsyncAPI docs, it uses Jackson @JsonInclude to build the POJOs from JSON schemas:

    package com.example.app;
    
    import com.fasterxml.jackson.annotation.JsonInclude;
    
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class PassengerInfo {
    
    	public PassengerInfo () {
    	}
    
    	public PassengerInfo (
    		String passengerAction, 
    		String destination, 
    		java.math.BigDecimal passengerId, 
    		String tripId, 
    		String vipStatus) {
    		this.passengerAction = passengerAction;
    		this.destination = destination;
    		this.passengerId = passengerId;
    		this.tripId = tripId;
    		this.vipStatus = vipStatus;
    	}
    
    	private String passengerAction;
    	private String destination;
    	private java.math.BigDecimal passengerId;
    	private String tripId;
    	private String vipStatus;
    	public String getPassengerAction() {
    		return passengerAction;
    	}
    
    	public PassengerInfo setPassengerAction(String passengerAction) {
    		this.passengerAction = passengerAction;
    		return this;
    	}
    
    
    	public String getDestination() {
    		return destination;
    	}
    
    	public PassengerInfo setDestination(String destination) {
    		this.destination = destination;
    		return this;
    	}
    
    
    	public java.math.BigDecimal getPassengerId() {
    		return passengerId;
    	}
    
    	public PassengerInfo setPassengerId(java.math.BigDecimal passengerId) {
    		this.passengerId = passengerId;
    		return this;
    	}
    
    
    	public String getTripId() {
    		return tripId;
    	}
    
    	public PassengerInfo setTripId(String tripId) {
    		this.tripId = tripId;
    		return this;
    	}
    
    
    	public String getVipStatus() {
    		return vipStatus;
    	}
    
    	public PassengerInfo setVipStatus(String vipStatus) {
    		this.vipStatus = vipStatus;
    		return this;
    	}
    
    	public String toString() {
    		return "PassengerInfo ["
    		+ " passengerAction: " + passengerAction
    		+ " destination: " + destination
    		+ " passengerId: " + passengerId
    		+ " tripId: " + tripId
    		+ " vipStatus: " + vipStatus
    		+ " ]";
    	}
    }
    
    

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭

    Well … from the Solace AWS Broker "Try Me!" page I tried sending both binary and text messages.

    The payload in the message text box was

    {\"name\":\"Miles Archer\"}
    

    Changed my simple PersonEvent to be

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class PersonEvent {
    
        @JsonProperty("name")
        private String name;
    
        public String getName() {
            return name;
    }
    
        public void setName(String name) {
            this.name = name;
    }
    
        public PersonEvent(String name) {
            this.name = name;
    }
    
    
        public PersonEvent() {
        
    
    
    }
    

    Still … I am not able to deserialize the message.

    So was hoping there was some lab or github examples with Solace that you can point me to that has this all working with both publishing and receiving messages.

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭

    Posted the following to Github as an example …

    https://github.com/dakotahNorth/HelloWorldSpringCloudStream

  • Aaron
    Aaron Member, Administrator, Moderator, Employee Posts: 638 admin
    #8 Answer ✓

    I cloned your code, even installed a JDK21. Got it working by moving the Person event class out of the Application class and into it's own class.

    Application.java

    @SpringBootApplication
    public class HelloWorldSpringCloudStreamApplication {
    
        public static void main(String... args) {
            SpringApplication.run(HelloWorldSpringCloudStreamApplication.class, args);
    
        }
    
        @Bean
        public Consumer<PersonEvent> exampleEventConsumer() {
    
            return personEvent -> {
                System.out.println("Received event: " + personEvent.getName());
    
            };
        }
    }
    

    PersonEvent.java

    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class PersonEvent {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "PersonEvent, name="+name;
        }
    }
    

    Also, you were escaping your quotes in your payload..? Here's a handy command-line tool my colleague wrote that is useful for things like this: https://github.com/SolaceLabs/solace-tryme-cli Just download the latest release, that's what I use:

    $ ./stm send -t try-me -m '{"name":"Aaron"}'
    

    Hope that's enough to get you going now..! 👍🏼

  • marc
    marc Member, Administrator, Moderator, Employee Posts: 959 admin
    edited April 16 #9

    @dakotahnorth, as you see in the Spring Cloud Stream documentation the framework automatically provides this deserialization for you as long as it can understand the payload.

    We have examples of it in our samples repo here: https://github.com/SolaceSamples/solace-samples-spring

    • see the cloud-stream-sink for the simplest of examples:https://github.com/SolaceSamples/solace-samples-spring/tree/master/cloud-stream-sink

    Note that the framework provides these MessageConverters and if you need others you can see the "User-defined Message Converters" section right after that one.

    FYI @Aaron no need to do the serialization and deserialization yourself!

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭
    edited April 16 #10

    Urgh … so embarrassing! Since the documentation had the Person class as part of the same class, I just figured it was the "correct" way.

    Thank you for point that out, as well as, the quotes!

    SUPER, SUPER helpful!

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭
    edited April 16 #11

    Thanks Marc!

    The sink example is helpful … and for "the simplest of examples", would be great to see this be even simpler!

    Instead of

    channel.send(MessageBuilder.withPayload(new SensorReading("test", 50, BaseUnit.FAHRENHEIT)).build());
    

    For example … with the help from above, this is what I am now doing with a StreamBridge

    streamBridge.send("try-me", new PersonPOJO("Miles Archer"));
    
    

  • dakotahnorth
    dakotahnorth Member Posts: 8 ✭✭

    Re the Person code above …

    @JsonInclude(JsonInclude.Include.NON_NULL)
    

    Isn't needed. Works with just a POJO with no special annotations.

  • marc
    marc Member, Administrator, Moderator, Employee Posts: 959 admin

    Awesome @dakotahnorth, glad you figured it out :)

    When you commented this which sample are you referring to? It's been a bit since I looked through these samples, but usually we use MessageBuilder when we are trying to show adding message headers.

    The sink example is helpful … and for "the simplest of examples", would be great to see this be even simpler!