0

I am trying to use domain model classes generated from schema using avro-maven-plugin 1.8.2 with spring-hateoas 1.0.0.RELEASE (via Spring Boot 2.2.0.RC1). The Spring MVC 5.2.0.RELEASE framework throws org.springframework.http.converter.HttpMessageNotWritableException during serialization of the EntityModel created by the @RestController. The EntityModel is assembled using a SimpleIdentifiableRepresentationModelAssembler sub-class.

Mapping the Avro objects to POJOs before passing them to the representation model assembler works as expected during serialization, but duplicating the domain model won't scale for this application in the long run. I've read about custom media types and I could probably do something like this for, say, application/hal+avro+json, but that's a deep rabbit hole to go down for a set of objects that, outside of the representation model, are easily serialized to simple JSON. Especially since REST clients don't actually need the Avro schema embedded in these objects... yet. Internal consumers of the Avro do need it though.

FundController.java:

@Autowired
private final FundRepresentationModelAssembler assembler;

...

@GetMapping(value = "/funds/{id}")
public ResponseEntity<EntityModel<Fund>> findOne(@PathVariable final String id) {
  return this.repo.findById(id)
    .map(this.assembler::toModel)
    .map(ResponseEntity::ok)
    .orElse(ResponseEntity.notFound().build());
}

FundRepresentationModelAssembler.java:

@Component
public class FundRepresentationModelAssembler extends SimpleIdentifiableRepresentationModelAssembler<Fund> {
    public FundRepresentationModelAssembler() {
        super(FundController.class);
    }
}

Model pom.xml:

<plugins>
  <plugin>
    <groupId>org.apache.avro</groupId>
    <artifactId>avro-maven-plugin</artifactId>
    <version>1.8.2</version>
    <executions>
      <execution>
        <phase>generate-sources</phase>
        <goals>
          <goal>schema</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
  ...
</plugins>

Model schema:

{"namespace": "org.example.model",
 "type": "record",
 "name": "Fund",
 "fields": [
     {"name": "id", "type": "string"},
     {"name": "description",  "type": "string"}
 ]
}

The nicely formatted error is:

org.springframework.http.converter.HttpMessageNotWritableException:
  Could not write JSON:
    Not an array: {"type":"record","name":"Fund","namespace":"org.example.model","fields":[{"name":"id","type":"string"},{"name":"description","type":"string"}]}

nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
  Not an array: {"type":"record","name":"Fund","namespace":"org.example.model","fields":[{"name":"id","type":"string"},{"name":"description","type":"string"}]} 

through reference chain: org.springframework.hateoas.EntityModel["content"]
  ->org.example.model.Fund["schema"]
    ->org.apache.avro.Schema$RecordSchema["elementType"]

What I'm hoping exists is a way to pass "serialization hints" down through the HATEOAS framework to Jackson.

1 Answers1

2

This turned out to be the easiest possible thing, once I understood how Spring HATEOAS EntityModel works. It has no special magic, it just uses @JsonUnwrapped so Jackson serializes its content field at the same level as its links. The Jackson ObjectMapper provided by Spring assumes the content is a POJO unless I register something more appropriate. Jackson provides jackson-dataformat-avro. All that was necessary was to register that with Spring:

@SpringBootApplication
public class ChartOfAccounts {
    public static void main(String[] args) {
        SpringApplication.run(ChartOfAccounts.class, args);
    }

    @Bean
    public AvroModule avroDataFormat() {
        return new AvroModule();
    }
}