Skip to content

Commit

Permalink
add: example app
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenik committed Aug 16, 2018
1 parent 9df4fbb commit b3a7c74
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 0 deletions.
68 changes: 68 additions & 0 deletions example/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>ru.zhenik.akka</groupId>
<artifactId>example</artifactId>
<version>1.0-SNAPSHOT</version>


<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<config.version>1.3.3</config.version>
<consul.client.version>1.2.1</consul.client.version>
<akka.http.version>10.1.3</akka.http.version>
<akka.actor.version>2.5.14</akka.actor.version>
</properties>

<dependencies>

<!--Config-->
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>${config.version}</version>
</dependency>



<!-- AKKA START -->
<!--HTTP-->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http_2.12</artifactId>
<version>${akka.http.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-http-core_2.12</artifactId>
<version>${akka.http.version}</version>
</dependency>
<!--Actor Model-->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.12</artifactId>
<version>${akka.actor.version}</version>
</dependency>
<!--Streams-->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-stream_2.12</artifactId>
<version>${akka.actor.version}</version>
</dependency>
<!-- AKKA END -->

<!-- HashiCorp Consul client -->
<!-- https://github.com/rickfast/consul-client -->
<dependency>
<groupId>com.orbitz.consul</groupId>
<artifactId>consul-client</artifactId>
<version>${consul.client.version}</version>
</dependency>
<!-- HashiCorp END -->

</dependencies>
</project>
44 changes: 44 additions & 0 deletions example/src/main/java/ru/zhenik/akka/example/AppConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ru.zhenik.akka.example;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

public class AppConfiguration {
public final String appId;
public final String serviceName;
public final String host;
public final Integer port;
public final ServiceDiscoveryConfiguration serviceDiscoveryConfiguration;

private AppConfiguration(final String appId){
// load application.conf file
Config config = ConfigFactory.load();

this.appId = appId;
this.serviceName = config.getString("service.name");
this.host = config.getString("application.host");
this.port = config.getInt("application.port");
this.serviceDiscoveryConfiguration =
new ServiceDiscoveryConfiguration(
config.getString("discovery.host"),
config.getInt("discovery.port"),
config.getLong("discovery.healthcheck-timeout")
);

}

public static AppConfiguration loadConfig(final String appId) {
return new AppConfiguration(appId);
}

public final static class ServiceDiscoveryConfiguration {
public final String host;
public final int port;
public final long healthCheckTimeout;
private ServiceDiscoveryConfiguration(String host, int port, long healthCheckTimeout) {
this.host = host;
this.port = port;
this.healthCheckTimeout = healthCheckTimeout;
}
}
}
51 changes: 51 additions & 0 deletions example/src/main/java/ru/zhenik/akka/example/Boot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ru.zhenik.akka.example;

import akka.NotUsed;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectHttp;
import akka.http.javadsl.Http;
import akka.http.javadsl.ServerBinding;
import akka.http.javadsl.model.HttpRequest;
import akka.http.javadsl.model.HttpResponse;
import akka.stream.ActorMaterializer;
import akka.stream.Materializer;
import akka.stream.javadsl.Flow;
import java.util.UUID;
import java.util.concurrent.CompletionStage;
import ru.zhenik.akka.example.infrastructure.discovery.DiscoveryAgentActor;
import ru.zhenik.akka.example.interfaces.rest.AppResource;

public class Boot {

public static void main(String[] args) {
// config
final String appId = UUID.randomUUID().toString();
final AppConfiguration appConfig = AppConfiguration.loadConfig(appId);

// actor system init
final ActorSystem system = ActorSystem.create();
final Materializer materializer = ActorMaterializer.create(system);

// service discovery actor
final ActorRef serviceDiscoveryActor = system.actorOf(DiscoveryAgentActor.props(appConfig), "example-app-consul-service");

// http init
final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = new AppResource(appConfig).routes().flow(system, materializer);
final CompletionStage<ServerBinding> binding = Http
.get(system)
.bindAndHandle(
routeFlow,
ConnectHttp.toHost(appConfig.host, appConfig.port),
materializer
);

// exception handling
binding.exceptionally(failure -> {
System.err.println("Something very bad happened! " + failure.getMessage());
system.terminate();
return null;
});

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package ru.zhenik.akka.example.infrastructure.discovery;

import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.actor.Props;
import com.google.common.net.HostAndPort;
import com.orbitz.consul.AgentClient;
import com.orbitz.consul.Consul;
import com.orbitz.consul.NotRegisteredException;
import com.orbitz.consul.model.agent.ImmutableRegistration;
import com.orbitz.consul.model.agent.Registration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import ru.zhenik.akka.example.AppConfiguration;
import scala.concurrent.duration.FiniteDuration;

/**
*
* */
public class DiscoveryAgentActor extends AbstractActor {

public static Props props(AppConfiguration config) {
return Props.create(DiscoveryAgentActor.class, () -> new DiscoveryAgentActor(config));
}

private final AppConfiguration configuration;
private final Consul consul;
private final AgentClient agentClient;
private final FiniteDuration SCHEDULED_WORK_DELAY;

public DiscoveryAgentActor(AppConfiguration configuration){
this.configuration = configuration;
this.SCHEDULED_WORK_DELAY = new FiniteDuration(configuration.serviceDiscoveryConfiguration.healthCheckTimeout, TimeUnit.SECONDS);

// todo: terminate system if error occur while connecting to consul
// get consul connection
this.consul = Consul
.builder()
.withHostAndPort(
HostAndPort.fromParts(
configuration.serviceDiscoveryConfiguration.host,
configuration.serviceDiscoveryConfiguration.port)
)
.build();

// get agent
agentClient = consul.agentClient();
// set registration config
Registration service = ImmutableRegistration.builder()
.id(configuration.appId)
.name(configuration.serviceName)
.port(configuration.port)
.address(configuration.host)
.check(Registration.RegCheck.ttl(configuration.serviceDiscoveryConfiguration.healthCheckTimeout))
.tags(Collections.singletonList("tag1"))
.meta(Collections.singletonMap("version", "1.0"))
.build();

// register service
agentClient.register(service);
// check in with Consul, serviceId required only. client will prepend "service:" for service level checks.
// Note that you need to continually check in before the TTL expires, otherwise your service's state will be marked as "critical".
}

@Override
public void preStart() {
getSelf().tell("Do Scheduled Work", ActorRef.noSender());
}

@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals("Do Scheduled Work", work -> {
sendHealthCheck();
context().system()
// send each (10seconds default) health-check to consul
.scheduler()
.schedule(
new FiniteDuration(5, TimeUnit.SECONDS),
SCHEDULED_WORK_DELAY,
healthCheck(),
getContext().dispatcher()
);
})
.build();
}

private void sendHealthCheck() {
try {
agentClient.pass(configuration.appId, configuration.serviceName +" alive and reachable");
} catch (NotRegisteredException e) {
e.printStackTrace();
getContext().getSystem().terminate();
}
System.out.println("Health check has been sent");
}

private Runnable healthCheck() {
return this::sendHealthCheck;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.zhenik.akka.example.interfaces.rest;

import static akka.http.javadsl.server.Directives.complete;
import static akka.http.javadsl.server.Directives.get;
import static akka.http.javadsl.server.Directives.path;
import static akka.http.javadsl.server.Directives.post;
import static akka.http.javadsl.server.Directives.route;

import akka.http.javadsl.model.ContentTypes;
import akka.http.javadsl.model.HttpEntities;
import akka.http.javadsl.model.StatusCodes;
import akka.http.javadsl.server.Route;
import ru.zhenik.akka.example.AppConfiguration;

public final class AppResource {

public final AppConfiguration config;
public final String healthResponse;

public AppResource(AppConfiguration config) {
this.config = config;
this.healthResponse = String
.format("ok \nservice-name:[%s]\napp-id:[%s]\nhost&port:[%s]", config.serviceName,
config.appId, config.host + ":" + config.port);
}

public Route routes() {

Route managementRoutes = path("health", () ->
get(() ->
complete(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, healthResponse))
)
);
Route appRoutes = path("app", () ->
post(() ->
complete(StatusCodes.OK)
)
);

return route(managementRoutes, appRoutes);

}
}
21 changes: 21 additions & 0 deletions example/src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
application {
// use 0.0.0.0 instead localhost
// https://github.com/moby/moby/issues/23082
host = "0.0.0.0"
host = ${?APPLICATION_HOST}
port = 3000
port = ${?APPLICATION_PORT}
}
// will be use when register in consul
service.name = "example-app"

// self registration pattern
// https://microservices.io/patterns/self-registration.html
discovery {
host = "0.0.0.0"
host = ${?DISCOVERY_HOST}
port = 8500
port = ${?DISCOVERY_PORT}
healthcheck-timeout = 10
healthcheck-timeout = ${?DISCOVERY_HEALTH_TIMEOUT}
}

0 comments on commit b3a7c74

Please sign in to comment.