Springboot integration. (#256)

This commit is contained in:
Artur Souza 2020-03-30 09:49:31 -07:00 committed by GitHub
parent 61e8a416d7
commit 4611f40cb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 354 additions and 167 deletions

3
.gitignore vendored
View File

@ -49,3 +49,6 @@ hs_err_pid*
/docs/dapr-sdk
/proto/dapr
/proto/daprclient
# macOS
.DS_Store

View File

@ -68,6 +68,12 @@ For a Maven project, add the following to your `pom.xml` file:
<artifactId>dapr-sdk-actors</artifactId>
<version>0.3.0</version>
</dependency>
<!-- Dapr's SDK integration with SpringBoot (optional). -->
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-springboot</artifactId>
<version>0.4.0-SNAPSHOT</version>
</dependency>
<!-- If needed, resolve version conflict of okhttp3. -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
@ -103,6 +109,8 @@ dependencies {
compile('io.dapr:dapr-sdk:0.3.0')
// Dapr's SDK for Actors (optional).
compile('io.dapr:dapr-sdk-actors:0.3.0')
// Dapr's SDK integration with SpringBoot (optional).
compile('io.dapr:dapr-sdk-springboot:0.4.0-SNAPSHOT')
// If needed, force conflict resolution for okhttp3.
configurations.all {

View File

@ -73,6 +73,11 @@
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-springboot</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-actors</artifactId>

View File

@ -12,6 +12,8 @@ import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import java.time.Duration;
/**
* Service for Actor runtime.
* 1. Build and install jars:
@ -36,7 +38,16 @@ public class DemoActorService {
CommandLine cmd = parser.parse(options, args);
// If port string is not valid, it will throw an exception.
int port = Integer.parseInt(cmd.getOptionValue("port"));
final int port = Integer.parseInt(cmd.getOptionValue("port"));
// Idle timeout until actor instance is deactivated.
ActorRuntime.getInstance().getConfig().setActorIdleTimeout(Duration.ofSeconds(30));
// How often actor instances are scanned for deactivation and balance.
ActorRuntime.getInstance().getConfig().setActorScanInterval(Duration.ofSeconds(10));
// How long to wait until for draining an ongoing API call for an actor instance.
ActorRuntime.getInstance().getConfig().setDrainOngoingCallTimeout(Duration.ofSeconds(10));
// Determines whether to drain API calls for actors instances being balanced.
ActorRuntime.getInstance().getConfig().setDrainBalancedActors(true);
// Register the Actor class.
ActorRuntime.getInstance().registerActor(DemoActorImpl.class);

View File

@ -38,7 +38,6 @@ mvn install
The first Java class is `DemoActorService`. Its job is to register an implementation of `DemoActor` in the Dapr's Actor runtime. In `DemoActorService.java` file, you will find the `DemoActorService` class and the `main` method. See the code snippet below:
```java
@SpringBootApplication
public class DemoActorService {
public static void main(String[] args) throws Exception {

View File

@ -44,18 +44,14 @@ public class Subscriber {
```
`DaprApplication.start()` Method will run an Spring Boot application that registers the `SubscriberController`, which exposes the message retrieval as a POST request. The Dapr's sidecar is the one that performs the actual call to the controller, based on the pubsub features.
This Spring Controller handles the message endpoint, Printing the recieved message which is recieved as the POST body. See the code snippet below:
This Spring Controller handles the message endpoint, Printing the message which is received as the POST body. The topic subscription in Dapr is handled automatically via the `@Topic` annotation. See the code snippet below:
```java
@RestController
public class SubscriberController {
///...
@GetMapping("/dapr/subscribe")
public byte[] daprConfig() throws Exception {
return SERIALIZER.serialize(new String[] { "message" });
}
@PostMapping(path = "/message")
@Topic(name = "testingtopic")
@PostMapping(path = "/testingtopic")
public Mono<Void> handleMessage(@RequestBody(required = false) byte[] body,
@RequestHeader Map<String, String> headers) {
return Mono.fromRunnable(() -> {

View File

@ -5,6 +5,7 @@
package io.dapr.examples.pubsub.http;
import io.dapr.Topic;
import io.dapr.client.domain.CloudEvent;
import io.dapr.serializer.DefaultObjectSerializer;
import org.springframework.web.bind.annotation.GetMapping;
@ -22,22 +23,13 @@ import java.util.Map;
@RestController
public class SubscriberController {
/**
* Dapr's default serializer/deserializer.
*/
private static final DefaultObjectSerializer SERIALIZER = new DefaultObjectSerializer();
@GetMapping("/dapr/subscribe")
public byte[] daprConfig() throws Exception {
return SERIALIZER.serialize(new String[]{"testingtopic"});
}
/**
* Handles a registered publish endpoint on this app.
* @param body The body of the http message.
* @param headers The headers of the http message.
* @return A message containing the time.
*/
@Topic(name = "testingtopic")
@PostMapping(path = "/testingtopic")
public Mono<Void> handleMessage(@RequestBody(required = false) byte[] body,
@RequestHeader Map<String, String> headers) {

View File

@ -10,6 +10,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dapr's HTTP callback implementation via SpringBoot.
* Scanning package io.dapr.springboot is required.
*/
@SpringBootApplication(scanBasePackages = {"io.dapr.springboot", "io.dapr.examples"})
public class DaprApplication {

View File

@ -252,6 +252,7 @@
<module>sdk-autogen</module>
<module>sdk</module>
<module>sdk-actors</module>
<module>sdk-springboot</module>
<module>examples</module>
</modules>

168
sdk-springboot/pom.xml Normal file
View File

@ -0,0 +1,168 @@
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-parent</artifactId>
<version>0.4.0-SNAPSHOT</version>
</parent>
<artifactId>dapr-sdk-springboot</artifactId>
<packaging>jar</packaging>
<version>0.4.0-SNAPSHOT</version>
<name>dapr-sdk-springboot</name>
<description>SDK extension for Springboot</description>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>libs-release</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<properties>
<maven.deploy.skip>false</maven.deploy.skip>
</properties>
<dependencies>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-actors</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.gmazzo</groupId>
<artifactId>okhttp-mock</artifactId>
<version>1.3.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<outputDirectory>target/jacoco-report/</outputDirectory>
</configuration>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>80%</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.springboot;
import io.dapr.Topic;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
public class DaprBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean == null) {
return null;
}
subscribeToTopics(bean.getClass());
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private static void subscribeToTopics(Class clazz) {
if (clazz == null) {
return;
}
subscribeToTopics(clazz.getSuperclass());
for (Method method : clazz.getDeclaredMethods()) {
Topic topic = method.getAnnotation(Topic.class);
if (topic == null) {
continue;
}
String topicName = topic.name();
if ((topicName != null) && (topicName.length() > 0)) {
DaprRuntime.getInstance().addSubscribedTopic(topicName);
}
}
}
}

View File

@ -6,6 +6,7 @@
package io.dapr.springboot;
import io.dapr.actors.runtime.ActorRuntime;
import io.dapr.serializer.DefaultObjectSerializer;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -21,16 +22,21 @@ import reactor.core.publisher.Mono;
@RestController
public class DaprController {
@GetMapping("/")
public String index() {
return "Greetings from Dapr!";
}
/**
* Dapr's default serializer/deserializer.
*/
private static final DefaultObjectSerializer SERIALIZER = new DefaultObjectSerializer();
@GetMapping("/dapr/config")
public byte[] daprConfig() throws Exception {
return ActorRuntime.getInstance().serializeConfig();
}
@GetMapping("/dapr/subscribe")
public byte[] daprSubscribe() throws Exception {
return SERIALIZER.serialize(DaprRuntime.getInstance().listSubscribedTopics());
}
@PostMapping(path = "/actors/{type}/{id}")
public Mono<Void> activateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.springboot;
import java.util.HashSet;
import java.util.Set;
class DaprRuntime {
/**
* The singleton instance.
*/
private static volatile DaprRuntime instance;
private final Set<String> subscribedTopics = new HashSet<>();
/**
* Private constructor to make this singleton.
*/
private DaprRuntime() {
}
/**
* Returns an DaprRuntime object.
*
* @return An DaprRuntime object.
*/
public static DaprRuntime getInstance() {
if (instance == null) {
synchronized (DaprRuntime.class) {
if (instance == null) {
instance = new DaprRuntime();
}
}
}
return instance;
}
public synchronized void addSubscribedTopic(String topicName) {
if (!this.subscribedTopics.contains(topicName)) {
this.subscribedTopics.add(topicName);
}
}
public synchronized String[] listSubscribedTopics() {
return this.subscribedTopics.toArray(new String[0]);
}
}

View File

@ -41,6 +41,12 @@
<version>${dapr.sdk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>dapr-sdk-springboot</artifactId>
<version>${dapr.sdk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>

View File

@ -11,7 +11,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dapr's HTTP callback implementation via SpringBoot.
*/
@SpringBootApplication(scanBasePackages = {"io.dapr.it.actors.app"})
@SpringBootApplication(scanBasePackages = {"io.dapr.springboot", "io.dapr.it.actors.app"})
public class TestApplication {
/**

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.it.actors.app;
import io.dapr.actors.runtime.ActorRuntime;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
/**
* SpringBoot Controller to handle callback APIs for Dapr.
*/
@RestController
class TestController {
@GetMapping("/")
public String index() {
return "Greetings from Dapr!";
}
@GetMapping("/dapr/config")
public byte[] daprConfig() throws Exception {
return ActorRuntime.getInstance().serializeConfig();
}
@PostMapping(path = "/actors/{type}/{id}")
public Mono<Void> activateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {
return ActorRuntime.getInstance().activate(type, id);
}
@DeleteMapping(path = "/actors/{type}/{id}")
public Mono<Void> deactivateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {
return ActorRuntime.getInstance().deactivate(type, id);
}
@PutMapping(path = "/actors/{type}/{id}/method/{method}")
public Mono<byte[]> invokeActorMethod(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("method") String method,
@RequestBody(required = false) byte[] body) {
return ActorRuntime.getInstance().invoke(type, id, method, body);
}
@PutMapping(path = "/actors/{type}/{id}/method/timer/{timer}")
public Mono<Void> invokeActorTimer(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("timer") String timer) {
return ActorRuntime.getInstance().invokeTimer(type, id, timer);
}
@PutMapping(path = "/actors/{type}/{id}/method/remind/{reminder}")
public Mono<Void> invokeActorReminder(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("reminder") String reminder,
@RequestBody(required = false) byte[] body) {
return ActorRuntime.getInstance().invokeReminder(type, id, reminder, body);
}
}

View File

@ -11,7 +11,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dapr's HTTP callback implementation via SpringBoot.
*/
@SpringBootApplication(scanBasePackages = {"io.dapr.it.actors.services.springboot"})
@SpringBootApplication(scanBasePackages = {"io.dapr.springboot", "io.dapr.it.actors.services.springboot"})
public class DaprApplication {
/**

View File

@ -1,62 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr.it.actors.services.springboot;
import io.dapr.actors.runtime.ActorRuntime;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
/**
* SpringBoot Controller to handle callback APIs for Dapr.
*/
@RestController
public class DaprController {
@GetMapping("/")
public String index() {
return "Greetings from Dapr!";
}
@GetMapping("/dapr/config")
public byte[] daprConfig() throws Exception {
return ActorRuntime.getInstance().serializeConfig();
}
@PostMapping(path = "/actors/{type}/{id}")
public Mono<Void> activateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {
return ActorRuntime.getInstance().activate(type, id);
}
@DeleteMapping(path = "/actors/{type}/{id}")
public Mono<Void> deactivateActor(@PathVariable("type") String type,
@PathVariable("id") String id) throws Exception {
return ActorRuntime.getInstance().deactivate(type, id);
}
@PutMapping(path = "/actors/{type}/{id}/method/{method}")
public Mono<byte[]> invokeActorMethod(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("method") String method,
@RequestBody(required = false) byte[] body) {
return ActorRuntime.getInstance().invoke(type, id, method, body);
}
@PutMapping(path = "/actors/{type}/{id}/method/timer/{timer}")
public Mono<Void> invokeActorTimer(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("timer") String timer) {
return ActorRuntime.getInstance().invokeTimer(type, id, timer);
}
@PutMapping(path = "/actors/{type}/{id}/method/remind/{reminder}")
public Mono<Void> invokeActorReminder(@PathVariable("type") String type,
@PathVariable("id") String id,
@PathVariable("reminder") String reminder,
@RequestBody(required = false) byte[] body) {
return ActorRuntime.getInstance().invokeReminder(type, id, reminder, body);
}
}

View File

@ -22,11 +22,6 @@ public class InputBindingController {
private static final List<String> messagesReceived = new ArrayList();
@GetMapping("/dapr/config")
public String daprConfig() throws Exception {
return "{}";
}
@PostMapping(path = "/sample123")
@PutMapping(path = "/sample123")
public void handleInputBinding(@RequestBody(required = false) String body) {

View File

@ -12,7 +12,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Service for subscriber.
*/
@SpringBootApplication(scanBasePackages = {"io.dapr.it.methodinvoke.http"})
@SpringBootApplication
public class MethodInvokeService {
public static final String SUCCESS_MESSAGE = "dapr initialized. Status: Running. Init Elapsed";

View File

@ -5,6 +5,7 @@
package io.dapr.it.pubsub.http;
import io.dapr.Topic;
import io.dapr.client.domain.CloudEvent;
import io.dapr.serializer.DefaultObjectSerializer;
import org.springframework.web.bind.annotation.*;
@ -22,21 +23,12 @@ public class SubscriberController {
private static final List<String> messagesReceived = new ArrayList();
/**
* Dapr's default serializer/deserializer.
*/
private static final DefaultObjectSerializer SERIALIZER = new DefaultObjectSerializer ();
@GetMapping("/dapr/subscribe")
public byte[] daprConfig() throws Exception {
return SERIALIZER.serialize(new String[] { "testingtopic" });
}
@GetMapping(path = "/messages")
public List<String> getMessages() {
return messagesReceived;
}
@Topic(name = "testingtopic")
@PostMapping(path = "/testingtopic")
public Mono<Void> handleMessage(@RequestBody(required = false) byte[] body,
@RequestHeader Map<String, String> headers) {

View File

@ -13,7 +13,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Service for subscriber.
*/
@SpringBootApplication(scanBasePackages = {"io.dapr.it.pubsub.http"})
@SpringBootApplication(scanBasePackages = {"io.dapr.springboot", "io.dapr.it.pubsub.http"})
public class SubscriberService {
public static final String SUCCESS_MESSAGE = "dapr initialized. Status: Running. Init Elapsed";

View File

@ -7,7 +7,6 @@ package io.dapr.it.secrets;
import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.response.LogicalResponse;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.DaprClientGrpc;

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
package io.dapr;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Topic {
/**
* Name of topic to be subscribed to.
*
* @return Topic's name.
*/
String name();
}