Skip to content

Implement a more user friendly api

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:

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

Edited by Ruben Backx

Merge request reports