vaadin / hilla

Build better business applications, faster. No more juggling REST endpoints or deciphering GraphQL queries. Hilla seamlessly connects Spring Boot and React to accelerate application development.
https://hilla.dev
Apache License 2.0
903 stars 57 forks source link

Make it easy to provide custom mappings for Java <-> TS #248

Open Artur- opened 3 years ago

Artur- commented 3 years ago

Description of the feature

Primary use case: Adding. support for non basic types, e.g. Pageable or UUID as a communications types either in Fusion itself or in an application project when the type is not supported in Fusion and straight forward bean handling is not preferred.

Goal: The user can map a given type to an already supported type (e.g. UUID -> String) or a collection of supported types, (e.g. Pageable -> bean with only offset and pageNumber). No need to redefine how an object is serialized to JSON, how the TS generated works or anything similar low level. This mapping affects both communications and how the TypeScript type is generated.

Implementation: Add a CustomTypeMapper<FROM,TO> class which you extend as e.g.

public class PageableMapper extends CustomTypeMapper<Pageable, PageableDTO> {
  public PageableMapper() {
        super(Pageable.class, PageableDTO.class);
    }

  @Override
  public PageableDTO toTransferType(Pageable pageable) {
    PageableDTO dto = new PageableDTO();
    dto.setPageNumber(pageable.getPageNumber();
    // ...
    return dto;
  }

  @Override
  public Pageable fromTransferType(PageableDTO dto) {
    Pageable pageable = new Pageable():
    ...
    return pageable;
  }
}

Fusion takes all CustomTypeMapper implementations into account when

  1. Generating TS. If an endpoint contains public List<Person> list(Pageable p) the TS looks like as if the endpoint had instead contained public List<Person> list(PageableDTO p)
  2. Serializing/deserializing. When an endpoint method returns an UUID it is converted into a String using the CustomTypeMapper and serialized accordingly.

Non-goals: This is not meant to be used for each and every type in the project to provide a DTO e.g. for a given app specific entity. If you want annotation based validation for the DTO you have to add annotations to the DTO class.

Legioth commented 3 years ago

Since this class is intended to be used as a specific non-generic subclass that is discovered on the classpath, the super(Pageable.class, PageableDTO.class); call seems quite redundant as the same information can also be extracted from the class definition using gentyref.

haijian-vaadin commented 3 years ago

@Legioth @Artur- Would this global scoped CustomTypeMapper be enough? Should we also support class/method level of mappers? So that each class/method can specify a different mapper by using some annotation, like @UseMapper(xxxx)?

Artur- commented 3 years ago

Global scoped only. It sounds horrible to have a project where the same data type is sent over the wire in different formats depending on the class/method

haijian-vaadin commented 3 years ago

Global scoped only. It sounds horrible to have a project where the same data type is sent over the wire in different formats depending on the class/method

Got it, ping @gilberto-torrezan

Legioth commented 3 years ago

With anything based on whatever declarative stuff happens to be picked up from your classpath, there should also be a way of imperatively overriding. But that override should still also be global in nature, e.g. by defining a CustmTypeMapperRegistry as a Spring bean and then making it possible to replace or supplement the default bean.

haijian-vaadin commented 3 years ago

A prototype in https://github.com/vaadin/flow/compare/feature/pageable.