Looking at Microsoft Build 2020, Dapr of Distributed Application Runtime seemed to be interesting. Tutorial was Node.js, so I made a sample using Dapr from Quarkus while referring to it.
See below for code https://github.com/koduki/example-dapr/tree/v01/api-with-java
Dapr is a framework developed by Microsoft that simplifies the implementation of microservices by implementing non-functional requirements such as inter-service invocation, state management, and inter-service messaging with a sidecar (Proxy). Since it is developed by OSS, it can be used from the following.
I thought that it was the same as a service mesh such as Istio because it was a sidecar, but when I listened to the session, it felt a little different, and abstracted file and state management (data persistence), or queuing such as Kafka. There seemed to be a role to play. It's more like Java EE's JNDI, DataSource, or JCA (Java Connector Architecture). ~~ If you're an uncle ~~ It's a chance to say, "This is the one I did at Shinkenzemi!"
If it is a Java EE container such as Weblogic, I think that this area is in the same memory space and talks with T3 in the first place, but Dapr and each application talk with HTTP or gRPC.
Let's move it for the time being before doing the actual tutorial. This time gRPC is a hassle, so we'll implement REST. For the time being, create a Quarkus template with the mvn command and execute it.
$ mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create \
-DprojectGroupId=dev.nklab \
-DprojectArtifactId=dapr-app \
-DprojectVersion=1.0.0-SNAPSHOT \
-DclassName="dev.nklab.example.dapr.HelloResource"
$ cd dapr-app
$ ./mvnw quarkus:dev
Try accessing with curl from another terminal.
$ curl http://localhost:8080/hello
hello
Install Dapr when you can confirm the operation of the application. k8s works without any special, but Docker seems to need to be installed in advance.
$ curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash
$ dapr init
The installation is now complete. If you get the following error, you probably forgot dapr init
.
exec: "daprd": executable file not found in $PATH
Next, wrap the Quarkus application created earlier with Dapr with the following command and execute it.
$ dapr run --app-id javaapp --app-port 8080 --port 3500 ./mvnw quarkus:dev
...
ℹ️ Updating metadata for app command: ./mvnw quarkus:dev
✅ You're up and running! Both Dapr and your app logs will appear here.
--app-port
is the Quarkus port number and --port
is the Dapr port number. Now let's access Dapr with curl.
$ curl http://localhost:3500/v1.0/invoke/javaapp/method/hello
hello
Since Dapr is acting as a sidecar, that is, a Proxy, you can see that you could access the backside app with 3500
instead of 8080
.
The javaapp
part is the ʻaap-id specified at runtime earlier. It seems that it will be a process that links with the application on the back side with ʻinvoke
.
Now, perform Steps 1 to 5 of Hello World. The application to be implemented this time has the following structure.
A user sends a request to Application (Java) via Dapr, hits Dapr's API from the application and writes to Redis via Dapr. It's interesting not to go directly through Redis for data persistence, as the application only talks to Dapr.
As a specification, the user sends the following POST request to Application.
{
"data": {
"orderId": "42"
}
}
This data is stored in Redis in the following format:
[{
key: "order",
value:Store orderId here
}]
Also, when a GET request is sent to Application, the current orderId is returned.
Now let's implement the application. Since we will use JSON this time, add the library to Quarkus.
$ ./mvnw quarkus:add-extension -Dextensions="quarkus-resteasy-jsonb"
$ ./mvnw quarkus:add-extension -Dextensions="quarkus-resteasy-jackson"
Then modify the application as follows:
HelloResource.java
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class HelloResource {
@ConfigProperty(name = "daprapp.daprport")
String daprPort;
String stateStoreName = "statestore";
@GET
@Path("/order")
public Map<String, Object> order() throws IOException, InterruptedException {
return Map.of("orderId", get(stateUrl() + "/order").body());
}
@POST
@Path("/neworder")
public HttpResponse neworder(Map<String, Map<String, Object>> data) throws IOException, InterruptedException {
System.out.println("orderId: " + data.get("data").get("orderId"));
var items = List.of(Map.of("key", "order", "value", data.get("data").get("orderId")));
return post(stateUrl(), items);
}
private String stateUrl() {
return "http://localhost:" + daprPort + "/v1.0/state/" + stateStoreName;
}
private HttpResponse<String> post(String url, List<Map<String, Object>> items) throws IOException, InterruptedException, JsonProcessingException {
var mapper = new ObjectMapper();
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(items)))
.setHeader("Content-Type", "application/json")
.build();
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
private HttpResponse<String> get(String url) throws InterruptedException, IOException {
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.setHeader("Content-Type", "application/json")
.build();
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
}
I haven't done anything special as JAX-RS, so I won't go into details, but new order
is the endpoint for registration and ʻorder` is the endpoint for reference.
You are accessing http: // localhost: 3500 / v1.0 / state / statestore
in each method. This is the endpoint of Dapr's state management API.
This time, the substance of this state management API is Redis. The request and response of the state management API will be JSON as shown below.
[{
key:value,
value:value
}]
Then configure Redis for your state management implementation. However, it has already been set, so just check it.
Actually, a directory called components
is created at the timing of dapr run
. It seems that the statestore.yaml
here describes which store to connect to.
components/statestore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: actorStateStore
value: "true"
Perhaps if you rewrite this to RDB etc., it will be another implementation.
Now that the implementation is complete, let's check the operation. First, start Dapr.
$ dapr run --app-id javaapp --app-port 8080 --port 3500 ./mvnw quarkus:dev
Then make a request.
$ curl -X POST -H "Content-Type: application/json" -d '{"data": { "orderId": "41" } }' http://localhost:3500/v1.0/invoke/javaapp/method/neworder
{}
$ curl http://localhost:3500/v1.0/invoke/javaapp/method/order
{"orderId":"\"41\""}
I think you can confirm that the requested value you threw is stored and you can get it. You can also write POST with the Dapr command as follows.
$ dapr invoke --app-id javaapp --method neworder --payload '{"data": { "orderId": "41" } }'
For the time being, I tried using Dapr from Java. Since it is REST, I was able to implement it without any problems. Dapr has a Jakarta EE feeling more than Istio, and apart from Dapr itself, this idea itself seems to be in the right direction from the viewpoint of ease of development. It is a basic idea to hide the substance of services and data including DAO patterns, and non-functionals should be integrated into the infrastructure side as much as possible.
On the other hand, if the persistence layer also goes through Proxy, it seems that a certain amount of overhead is inevitable even if gRPC is used. I think that it will be required in the future how to deal with this area by design.
Dapr itself is still in its infancy and there are many rough edges, but I would like to touch it a little more in the future.
Then Happy Hacking!
Recommended Posts