Getting started with Spring Boot
Build configurations
You must first add the Cloudstate dependency to your project:
- Maven
-
<dependencies> <dependency> <groupId>io.cloudstate</groupId> <artifactId>cloudstate-springboot-support</artifactId> <version>0.5.1</version> </dependency> </dependencies>
- sbt
-
libraryDependencies += "io.cloudstate" % "cloudstate-springboot-support" % "0.5.1"
- gradle
-
compile group: 'io.cloudstate', name: 'cloudstate-springboot-support', version: '0.5.1'
Cloudstate applications are based on contracts created via gRPC, in the case of Maven-based Spring Boot applications you can use the following plugins to assist in this task:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocExecutable>/usr/local/bin/protoc</protocExecutable>
<protocArtifact>com.google.protobuf:protoc:3.9.1:exe:${os.detected.classifier}</protocArtifact>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Here we have an example of a pom.xml file with all the necessary parts present:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.cloudstate</groupId>
<artifactId>cloudstate-springboot-example</artifactId>
<version>0.5.1</version>
<properties>
<main.class>io.cloudstate.springboot.example.Main</main.class>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.cloudstate</groupId>
<artifactId>cloudstate-springboot-support</artifactId>
<version>0.5.1</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocExecutable>/usr/local/bin/protoc</protocExecutable>
<protocArtifact>com.google.protobuf:protoc:3.9.1:exe:${os.detected.classifier}</protocArtifact>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<from>
<image>adoptopenjdk/openjdk8-openj9:alpine-slim</image>
<credHelper></credHelper>
</from>
<to>
<image>cloudstateio/samples-springboot-shopping-cart</image>
<credHelper></credHelper>
<tags>
<tag>${project.version}</tag>
</tags>
</to>
<container>
<mainClass>${main.class}</mainClass>
<jvmFlags>
<jvmFlag>-XshareClasses</jvmFlag>
<jvmFlag>-Xquickstart</jvmFlag>
<jvmFlag>-XX:+UseG1GC</jvmFlag>
<jvmFlag>-XX:+UseStringDeduplication</jvmFlag>
</jvmFlags>
<ports>
<port>8080</port>
</ports>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
Remember to change the values of the main.class , repo.name , and version tags to their respective values
|
Subsequent source locations and build commands will assume the above Maven project, and may need to be adapted to your particular build tool and setup.
Protobuf files
The Xolstice Maven plugin assumes a location of src/main/proto
for your protobuf files.
In addition, it includes any protobuf files from your application dependencies in the protoc include path, so there’s nothing you need to do to pull in either the Cloudstate protobuf types, or any of the Google standard protobuf types, they are all automatically available for import.
So, if you were to build the example shopping cart application shown earlier in gRPC descriptors, you could simply paste that protobuf into src/main/proto/shoppingcart.proto
.
You may wish to also define the java package, to ensure the package name used conforms to Java package naming conventions:
option java_package = "com.example.shoppingcart";
Now if you run mvn compile
, you’ll find your generated protobuf files in target/generated-sources/protobuf/java
.
Write your Cloudstate function:
In general you will write your entity functions in the usual way as you would if you were using the version of Java support. For more information on how to create entity functions, see the specific documentation for event sourced and CRDT entities.
The differences will be explained later in this documentation. Below is an example of an EventSourced user entity:
/**
* An event sourced entity.
*/
@EventSourcedEntity
@CloudstateEntityBean
public class ShoppingCartEntity {
private final Map<String, Shoppingcart.LineItem> cart = new LinkedHashMap<>();
@EntityId
private String entityId;
@CloudstateContext
private EventSourcedContext context;
@Autowired
private RuleService ruleService;
@Autowired
private ShoppingCartTypeConverter typeConverter;
@Snapshot
public Domain.Cart snapshot() {
return Domain.Cart.newBuilder()
.addAllItems(cart.values().stream().map(typeConverter::convert).collect(Collectors.toList()))
.build();
}
@SnapshotHandler
public void handleSnapshot(Domain.Cart cart) {
this.cart.clear();
for (Domain.LineItem item : cart.getItemsList()) {
this.cart.put(item.getProductId(), typeConverter.convert(item));
}
}
@EventHandler
public void itemAdded(Domain.ItemAdded itemAdded) {
Shoppingcart.LineItem item = cart.get(itemAdded.getItem().getProductId());
if (item == null) {
item = typeConverter.convert(itemAdded.getItem());
} else {
item =
item.toBuilder()
.setQuantity(item.getQuantity() + itemAdded.getItem().getQuantity())
.build();
}
cart.put(item.getProductId(), item);
}
@EventHandler
public void itemRemoved(Domain.ItemRemoved itemRemoved) {
cart.remove(itemRemoved.getProductId());
}
@CommandHandler
public Shoppingcart.Cart getCart() {
return Shoppingcart.Cart.newBuilder().addAllItems(cart.values()).build();
}
@CommandHandler
public Empty addItem(Shoppingcart.AddLineItem item, CommandContext ctx) {
if (!ruleService.isValidAmount(item)) {
ctx.fail("Cannot add negative quantity of to item" + item.getProductId());
}
ctx.emit(
Domain.ItemAdded.newBuilder()
.setItem(
Domain.LineItem.newBuilder()
.setProductId(item.getProductId())
.setName(item.getName())
.setQuantity(item.getQuantity())
.build())
.build());
return Empty.getDefaultInstance();
}
@CommandHandler
public Empty removeItem(Shoppingcart.RemoveLineItem item, CommandContext ctx) {
if (!cart.containsKey(item.getProductId())) {
ctx.fail("Cannot remove item " + item.getProductId() + " because it is not in the cart.");
}
ctx.emit(Domain.ItemRemoved.newBuilder().setProductId(item.getProductId()).build());
return Empty.getDefaultInstance();
}
}
To work Cloudstate requires that the descriptors of the protobuf’s files are explicitly registered. We have two ways to do this:
-
Via Spring Boot by creating a Spring Boot Configuration class and registering these types accordingly.
-
Programmatically, which we’ll explain later in Conventions and Restrictions.
Here is an example of a suitable configuration class:
import com.example.shoppingcart.Shoppingcart;
import com.google.protobuf.Descriptors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DescriptorsConfiguration {
@Bean
public Descriptors.ServiceDescriptor shoppingCartEntityServiceDescriptor() {
return Shoppingcart.getDescriptor().findServiceByName("ShoppingCart");
}
@Bean
public Descriptors.FileDescriptor[] shoppingCartEntityFileDescriptors() {
return new Descriptors.FileDescriptor[]{com.example.shoppingcart.persistence.Domain.getDescriptor()};
}
}
Then write your simple main class in the Spring boot style:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import io.cloudstate.springboot.starter.autoconfigure.EnableCloudstate;
@EnableCloudstate
@SpringBootApplication
public class Main {
public static void main(String[] args) { SpringApplication.run(Main.class, args); }
}
In Cloudstate Spring Boot support it is not necessary to register your entities explicitly, instead you just use the annotation @EnableCloudstate to tell Spring what to do
|
Then run the application in the same way as you would any other springboot application with mvn spring-boot:run
command.