Implement a more user friendly api
This MR aims to implement a more user friendly API to retrieve data.
The problem
Currently, when trying to retrieve nested data from core, multiple API controllers need to be injected. Take the following example of retrieving all submissions in an edition:
@Autowired
private EditionControllerApi editionApi;
@Autowired
private ModuleControllerApi moduleApi;
@Autowired
private AssignmentControllerApi assignmentApi;
// ...
@GetMapping("{id}/submissions")
public String getAllSubmissions(@PathVariable Long id, Model model) {
EditionDetailsDTO edition = editionApi.getEditionById(id).block();
List<ModuleDetailsDTO> modules = moduleApi.getModulesByIds(edition.getModules().stream()
.map(ModuleSummaryDTO::getId).toList()).collectList().block();
List<AssignmentDetailsDTO> assignments = assignmentApi.getAssignmentsByIds(module.getAssignments().stream()
.map(AssignmentSummaryDTO::getId).toList()).collectList().block();
model.addAttribute("assignments", assignments);
return "submissions";
}
<th:block th:each="assignment : ${assignments}">
<div th:each="submission : ${assignment.submissions}"><!-- ... --></div>
</th:block>
Proposed solution
This MR implements a class for every entity in core with all data that is either present or can be lazily retrieved. The previous example can be written as follows:
@GetMapping("{id}/submissions")
public String getAllSubmissions(@PathVariable Long id, Model model) {
model.addAttribute("assignments", Edition.byId(id).getModules().bindLift(Module::getAssignments).get());
}
with idenitical html,
or:
<th:block th:each="module : ${@editionFinder.byId(editionId).getModules().get()}">
<th:block th:each="assignments : ${module.getAssignments().get()}">
<div th:each="submission : ${assignment.submissions}"><!-- ... --></div>
</th:block>
</th:block>
where the body of the method is simply:
model.addAttribute("editionId", id);
Caching
All requested entities are cached per request, with the exception of PersonEntity
, which is valid for a certain amount of time.
Extended Entities
In applications that require more data to be added to the core entities, the ExtendedEntityFinder
and ExtendedEntityRepository
can be used. For example, to define an edition with an additional hidden
property, the following classes need to be created:
ExtendedEdition.java
@Entity
public ExtendedEdition {
private Long id;
private Boolean hidden;
}
ExtendedEditionView.java
@Data
@SuperBuilder
@NoArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public ExtendedEditionView extends Edition {
private Boolean hidden;
}
ExtendedEditionRepository.java
public interface ExtendedEditionRepository extends ExtendedEntityRepository<ExtendedEdition, Long> {}
ExtendedEditionFinder.java
public class ExtendedEditionFinder extends ExtendedEntityFinder<Long, EditionDetailsDTO, Edition, ExtendedEdition, ExtendedEditionView, EditionCacheManager, EditionFinder, ExtendedEditionRepository> {
@Override
public void mapRepositoryData(ExtendedEditionView entity, ExtendedEdition databaseEntity) {
entity.setHidden(databaseEntity.getHidden());
}
}
ExtendedEditionView
can then be used exactly like Edition
.