Implement a more user friendly api

Open Ruben Backx requested to merge more-user-friendly-api into development

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:

private EditionControllerApi editionApi;
private ModuleControllerApi moduleApi;
private AssignmentControllerApi assignmentApi;
// ...
public String getAllSubmissions(@PathVariable Long id, Model model) {
    EditionDetailsDTO edition = editionApi.getEditionById(id).block();
    List<ModuleDetailsDTO> modules = moduleApi.getModulesByIds(edition.getModules().stream()
    List<AssignmentDetailsDTO> assignments = assignmentApi.getAssignmentsByIds(module.getAssignments().stream()
    model.addAttribute("assignments", assignments);
    return "submissions";
<th:block th:each="assignment : ${assignments}">
    <div th:each="submission : ${assignment.submissions}"><!-- ... --></div>

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:

public String getAllSubmissions(@PathVariable Long id, Model model) {
    model.addAttribute("assignments", Edition.byId(id).getModules().bindLift(Module::getAssignments).get());

with idenitical html,


<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>

where the body of the method is simply:

model.addAttribute("editionId", id);


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:

public ExtendedEdition {
    private Long id;
    private Boolean hidden;

@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public ExtendedEditionView extends Edition {
    private Boolean hidden;

public interface ExtendedEditionRepository extends ExtendedEntityRepository<ExtendedEdition, Long> {}

public class ExtendedEditionFinder extends ExtendedEntityFinder<Long, EditionDetailsDTO, Edition, ExtendedEdition, ExtendedEditionView, EditionCacheManager, EditionFinder, ExtendedEditionRepository> {
    public void mapRepositoryData(ExtendedEditionView entity, ExtendedEdition databaseEntity) {

ExtendedEditionView can then be used exactly like Edition.

Edited by Ruben Backx

Merge request reports