[Java - Crnk] Crnk Spring Boot example for the development of resource-oriented APIs

Crnk Spring Boot example

Crnk is a native resource-oriented rest library where resources and relationships are first class citizens and not just path mappings on methods. Model your application as resources and relationships and implement the repositories to access them. This simplifies development and opens up many, powerful new possibilities:

  • Fully transparent, automated HATEOAS setup.

  • Fetch complex object graphs with the inclusion of relationships to perform well even with mobile connections.

  • Let your API evolve with the introduction of new attributes and relationship and have it discoverable. Stop handing out RPC-style APIs where every further added, isolated service makes the system less understandable.

Prerequites

- [Spring Boot - https://spring.io/projects/spring-boot](https://spring.io/projects/spring-boot)

    Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

    See [Spring | Spring Quickstart Guide - https://spring.io/quickstart](https://spring.io/quickstart) to learn more.

Example

The following gives a brief example of how to setup Crnk with Spring Boot.

See crnk-framework/crnk-integration-examples/spring-boot-minimal-example at master · crnk-project/crnk-framework - https://github.com/crnk-project/crnk-framework/tree/master/crnk-integration-examples/spring-boot-minimal-example to learn more.

build.gradle

Add Crank dependencies to bundle.gradle.

Remember to make org.springframework.boot and org.springframework.boot:spring-boot-dependencies have the same version.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// build.gradle

plugins {
id 'org.springframework.boot' version '2.6.2'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id 'org.springframework.experimental.aot' version '0.11.1'
}

group = 'com.cloudolife.common.configuration.service'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
compileOnly {
extendsFrom annotationProcessor
}
}

repositories {
maven { url 'https://repo.spring.io/release' }
mavenCentral()
+ jcenter()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

+ implementation 'io.crnk:crnk-setup-spring-boot2'
+ implementation 'io.crnk:crnk-format-plain-json'
+ implementation 'io.crnk:crnk-home'
+
+ implementation 'org.junit.jupiter:junit-jupiter-api:5.5.1'
}

test {
useJUnitPlatform()
}

+ bootBuildImage {
+ builder = 'paketobuildpacks/builder:tiny'
+ environment = ['BP_NATIVE_IMAGE': 'true']
+ }
+
+ dependencyManagement {
+ imports {
+ mavenBom 'org.springframework.boot:spring-boot-dependencies:2.6.2'
+ mavenBom "io.crnk:crnk-bom:3.4.20210509072026"
+ }
+ }

JsonApiResource - Project.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Project.java

package io.crnk.example.springboot.microservice;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.crnk.core.resource.annotations.JsonApiId;
import io.crnk.core.resource.annotations.JsonApiResource;

@JsonApiResource(type = "projects")
public class Project {

@JsonApiId
private Long id;

@JsonProperty
private String name;

public Project() {
}

public Project(Long id, String name) {
this.id = id;
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

Repository - ProjectRepositoryImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ProjectRepositoryImpl.java

package io.crnk.example.springboot.microservice;

import io.crnk.core.queryspec.QuerySpec;
import io.crnk.core.repository.ResourceRepositoryBase;
import io.crnk.core.resource.list.ResourceList;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

@Component
public class ProjectRepositoryImpl extends ResourceRepositoryBase<Project, Long> {

private static final AtomicLong ID_GENERATOR = new AtomicLong(124);

private Map<Long, Project> projects = new HashMap<>();

public ProjectRepositoryImpl() {
super(Project.class);
}

@Override
public synchronized void delete(Long id) {
projects.remove(id);
}

@Override
public synchronized <S extends Project> S save(S project) {
if (project.getId() == null) {
project.setId(ID_GENERATOR.getAndIncrement());
}
projects.put(project.getId(), project);
return project;
}

@Override
public synchronized ResourceList<Project> findAll(QuerySpec querySpec) {
return querySpec.apply(projects.values());
}
}

SpringBootApplication - MinimalSpringBootApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// MinimalSpringBootApplication.java

package io.crnk.example.springboot.microservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@SpringBootApplication
@Import({TestDataLoader.class})
public class MinimalSpringBootApplication {

public static void main(String[] args) {
SpringApplication.run(MinimalSpringBootApplication.class, args);
System.out.println("visit http://127.0.0.1:8080/ in your browser");
}
}

Run

Run with Gradle:

1
$ gradlew :crnk-integration-examples:spring-boot-minimal-example:run

The service will be available at http://localhost:8080/

FAQs

java.util.Map io.crnk.spring.setup.boot.mvc.CrnkErrorController.getErrorAttributes(javax.servlet.http.HttpServletRequest, boolean)

Crnk error handling is not compatible with Spring Boot 2.5+.

1
2
3
4
5
// ColCommonConfigurationServiceJavaApplication.java

- //@SpringBootApplication
+ @SpringBootApplication(exclude = {io.crnk.spring.setup.boot.mvc.CrnkErrorControllerAutoConfiguration.class})
public class ColCommonConfigurationServiceJavaApplication {

See Crnk error handling is not compatible with Spring Boot 2.5 · Issue #816 · crnk-project/crnk-framework - https://github.com/crnk-project/crnk-framework/issues/816 to learn more.

References

[1] Crnk: Resource-oriented Rest Development with JSON:API - http://www.crnk.io/

[2] crnk-framework/crnk-integration-examples/spring-boot-minimal-example at master · crnk-project/crnk-framework - https://github.com/crnk-project/crnk-framework/tree/master/crnk-integration-examples/spring-boot-minimal-example

[3] crnk-framework/crnk-integration-examples at master · crnk-project/crnk-framework - https://github.com/crnk-project/crnk-framework/tree/master/crnk-integration-examples

[4] Crnk Documentation - http://www.crnk.io/releases/stable/documentation/

[5] JSON:API — Latest Specification (v1.0) - https://jsonapi.org/format/

[6] JSON:API — Recommendations - https://jsonapi.org/recommendations/

[7] Spring Boot - https://spring.io/projects/spring-boot

[8] Spring | Spring Quickstart Guide - https://spring.io/quickstart

[9] 3.7. Integration with Spring and Spring Boot | Crnk Documentation - http://www.crnk.io/releases/stable/documentation/#_integration_with_spring_and_spring_boot