Skip to main content

Adding a Feature End-to-End

A short worked example: suppose you want to add an endpoint that lists all abstract classes in a graph and surface them in the navigation tree with an italic style.

Step 1 — Backend use case

// services/select/ListAbstractClassesUseCase.java
public interface ListAbstractClassesUseCase {
List<ClassDTO> listAbstractClasses(GraphIdentifier graphIdentifier);
}

Step 2 — Service implementation

Either extend an existing service (if one already holds the same DatabasePort and is logically related — e.g. QueryClassService) or create a new services/select/AbstractClassQueryService that implements the use case and wires the DatabasePort via @RequiredArgsConstructor.

Step 3 — Controller endpoint

Add a new @GetMapping to AllClassesRESTController (or a new controller if a new path tier is involved). Keep the controller thin — call the use case, log on receive and respond.

@GetMapping("/abstract")
@Operation(summary = "list abstract classes", tags = {"class"})
public List<ClassDTO> listAbstract(@PathVariable String datasetName,
@PathVariable String graphURI,
@RequestHeader(...) String originURL) {
logger.info("Received GET request: \".../abstract\" from \"{}\".", originURL);
var result = listAbstractClassesUseCase.listAbstractClasses(...);
logger.info("Sending response to GET request: \".../abstract\" to \"{}\".", originURL);
return result;
}

Step 4 — Tests

  • Unit-test the service against the in-memory database (InMemoryDatabaseImpl).
  • Integration-test the endpoint with @SpringBootTest + MockMvc if the routing or DTO shape is non-trivial.

Step 5 — Frontend BackendConnection method

Export the updated contract from the backend and regenerate the frontend client:

cd backend
mvn exec:java@export-openapi # writes ../frontend/openapi.json — commit the result
cd ../frontend
npm run api:generate # writes src/lib/api/generated from ./openapi.json

This generates a function to call, based on the OpenAPI annotations on the controller in the backend. frontend/openapi.json is the committed contract and is checked by backend CI; the generated client itself is git-ignored, so every contributor regenerates it locally.

Call the new endpoint either by adding a method to the relevant store or by directly calling the client in a component.

const abstractClasses = listAbstract({
path = { datasetName: datasetName, graphURI: graphURI }
})

Step 6 — Reactive consumer

Either update the existing class-list reactive store or read on demand from a component. If the navigation tree needs to mark the abstract ones, extend build-nav-object.js to include the abstract flag on each class node, and update ClassEntry.svelte to render italic when the flag is set.

Step 7 — Tests

Vitest unit tests for any new logic in lib/. Manual smoke test through the UI; include a live Fuseki instance when the feature touches snapshots.

Step 8 — PR

Follow the squash commit format and the pre-merge checklist in CONTRIBUTING.md. Update CHANGELOG.md under ## [Unreleased] if the change is user-facing.