Rest API Controller (pattern)
Which is the best pattern to use in the controller?
Status | DONE |
---|---|
Impact | HIGH |
Driver | @Alessandro Domanico |
Approver | @Alessandro Domanico |
Stakeholders | @Niccolò Pasquetto @Vito Romano @Antonio Verni @Roberto C @Riccardo Costa |
Informed |
|
Due date | Mar 25, 2020 |
Outcome | Final pattern in the Outcome section |
Background
The REST API layer is still under design. Different ideas has been proposed up to now, but still we are not sure which is the best one to adopt for the project.
Initally, the https://openhospital.atlassian.net/browse/OP-4 was proposed. Then the https://openhospital.atlassian.net/browse/OP-6 has started and divided into a number of subtasks.
One of these subtask (the https://openhospital.atlassian.net/browse/OP-118) changed the initial proposal to something different while not yet completed (premature merge) and while other contributors were relying on the initially proposed pattern in other isses (e.g. https://openhospital.atlassian.net/browse/OP-178 and https://openhospital.atlassian.net/browse/OP-127)
So now the point is to adopt one of the two, with an agnostic comparison between different solutions.
Relevant data
almost 90% of ModelDTOs in “api” would be identical to Models in “core”
some ModelDTO (10%) may need a dedicated mapping
the Swagger documentation need to be coherent with the code
Options considered
| Option 1: | Option 2: | Option 3: |
---|---|---|---|
Description | Linear mapping | Automapping | AbstractController |
Pros and cons | doesn’t need more knowledge for Java Base contributors (more linear) makes the controllers ticker | offer a centralized point for models mapping can be customized or disabled where not required it needs annotations (less linear) it needs adjustments for Swagger documentation it binds to a 1:1 mapping that may be useless in the future | it should simplify the writing of a standard controller it works only for controllers managing one Model/DTO it binds to a 1:1 mapping that may be useless in the future it may lead to a “GOD Class” known problem |
Estimated cost | Small | small | SMALL |
Action items
These are the proposed steps in order to take a decision
Outcome
The final pattern approved pattern (temporary but the same for all OP-6 subtasks) is the following and it is implemented here https://github.com/informatici/openhospital-api/tree/OP-6-new-proposed-pattern
/openhospital-api/src/main/java/org/isf/shared/mapper/converter/ModelMapperConfig.java
package org.isf.shared.mapper.converter;
@Configuration
public class ModelMapperConfig {
@Autowired
protected BlobToByteArrayConverter blobToByteArrayConverter;
@Autowired
protected ByteArrayToBlobConverter byteArrayToBlobConverter;
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.addConverter(blobToByteArrayConverter);
modelMapper.addConverter(byteArrayToBlobConverter);
return modelMapper;
}
}
/openhospital-api/src/main/java/org/isf/shared/mapper/Mapper.java
package org.isf.shared.mapper;
public interface Mapper <FromType, ToType> {
public ToType map2DTO(FromType fromObj);
public FromType map2Model(ToType toObj);
public List<ToType> map2DTOList(List<FromType> list);
}
/openhospital-api/src/main/java/org/isf/shared/mapper/GenericMapper.java
package org.isf.shared.mapper;
public class GenericMapper<SourceType, DestType> implements Mapper<SourceType, DestType> {
@Autowired
protected ModelMapper modelMapper;
private Type sourceClass;
private Type destClass;
public GenericMapper(Class<SourceType> sourceClass, Class<DestType> destClass) {
this.sourceClass = sourceClass;
this.destClass = destClass;
}
@Override
public DestType map2DTO(SourceType fromObj) {
return modelMapper.map(fromObj, destClass);
}
@Override
public SourceType map2Model(DestType toObj) {
return modelMapper.map(toObj, sourceClass);
}
@Override
public List<DestType> map2DTOList(List<SourceType> list) {
return (List<DestType>) list.stream().map(it -> modelMapper.map(it, destClass)).collect(Collectors.toList());
}
}
/openhospital-api/src/main/java/org/isf/patient/mapper/PatientMapper.java (example)
package org.isf.patient.mapper;
@Component
public class PatientMapper extends GenericMapper<Patient, PatientDTO> {
public PatientMapper() {
super(Patient.class, PatientDTO.class);
}
}
Then in /openhospital-api/src/main/java/org/isf/patient/rest/PatientController.java (example)
@Autowired
protected PatientMapper mapper;
...
boolean isCreated = patientManager.newPatient(mapper.map2Model(newPatient));
...
List<PatientDTO> patientDTOS = mapper.map2DTOList(patients);
...
return ResponseEntity.ok(mapper.map2DTO(patient));
...
Open Hospital powered by ISF
2005 - 2016 ISF © Informatici senza frontiere - ONLUS