From 449da1a50204f16083d5323677ea9e911b60aaf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20G=C5=82odek?= Date: Wed, 6 May 2015 22:44:05 +0200 Subject: [PATCH 1/5] a nginx server conf initial version --- README.md | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/README.md b/README.md index 6a912af..dc46854 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,81 @@ psql auth_password -U postgres -f ./auth-password/src/main/resources/auth_entry. psql identity_manager -U postgres -f ./identity-manager/src/main/resources/identity.sql ``` +#### Setup CORS proxy + +Use NGINX or such to proxy with CORS headers and OPTION request responses. + +Crude fast solution: + +``` +http { + server { + listen 10000; + server_name localhost; + set $true 1; + more_set_headers "Access-Control-Allow-Origin: $http_origin"; + + location /auth-codecard/ { + proxy_pass http://localhost:8005; + if ($request_method = OPTIONS ) { + add_header Access-Control-Allow-Headers "Auth-Token, Content-Type"; + add_header Access-Control-Allow-Methods "GET, OPTIONS, POST, DELETE, PUT, PATCH"; + add_header Access-Control-Allow-Credentials "true"; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 200; + } + } + + location /auth-password/ { + proxy_pass http://localhost:8002; + if ($request_method = OPTIONS ) { + add_header Access-Control-Allow-Headers "Auth-Token, Content-Type"; + add_header Access-Control-Allow-Methods "GET, OPTIONS, POST, DELETE, PUT, PATCH"; + add_header Access-Control-Allow-Credentials "true"; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 200; + } + } + location /auth-fb/ { + proxy_pass http://localhost:8001; + if ($request_method = OPTIONS ) { + add_header Access-Control-Allow-Headers "Auth-Token, Content-Type"; + add_header Access-Control-Allow-Methods "GET, OPTIONS, POST, DELETE, PUT, PATCH"; + add_header Access-Control-Allow-Credentials "true"; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 200; + } + } + location /session/ { + proxy_pass http://localhost:8011; + if ($request_method = OPTIONS ) { + add_header Access-Control-Allow-Headers "Auth-Token, Content-Type"; + add_header Access-Control-Allow-Methods "GET, OPTIONS, POST, DELETE, PUT, PATCH"; + add_header Access-Control-Allow-Credentials "true"; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 200; + } + } + location /btc/ { + proxy_pass http://localhost:8011; + if ($request_method = OPTIONS ) { + add_header Access-Control-Allow-Headers "Auth-Token, Content-Type"; + add_header Access-Control-Allow-Methods "GET, OPTIONS, POST, DELETE, PUT, PATCH, UPGRADE"; + add_header Access-Control-Allow-Credentials "true"; + add_header Content-Length 0; + add_header Content-Type text/plain; + return 200; + } + } + } +} +``` + + ## Running Before starting anything make sure you have PostgreSQL, MongoDB and Redis up and running. From 69a4f406a1e78ab7996ab5c52911615b4ad3fb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20G=C5=82odek?= Date: Mon, 11 May 2015 14:00:41 +0200 Subject: [PATCH 2/5] removed frontend-server --- build.sbt | 4 ---- frontend-server/build.sbt | 22 ------------------- frontend-server/project/plugins.sbt | 1 - .../src/main/resources/application.conf | 12 ---------- .../src/main/scala/FrontendServer.scala | 18 --------------- 5 files changed, 57 deletions(-) delete mode 100644 frontend-server/build.sbt delete mode 100644 frontend-server/project/plugins.sbt delete mode 100644 frontend-server/src/main/resources/application.conf delete mode 100644 frontend-server/src/main/scala/FrontendServer.scala diff --git a/build.sbt b/build.sbt index 56857cc..23d4fba 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,6 @@ version := "1.0" lazy val `reactive-microservices` = (project in file(".")) -lazy val `frontend-server` = project in file("frontend-server") lazy val metricsCommon = project in file("metrics-common") @@ -42,7 +41,6 @@ val cleanAll = taskKey[Unit]("Cleans all subprojects") compileAll := { fork in compile := true - (compile in Compile in `frontend-server`).toTask.value (compile in Compile in `token-manager`).toTask.value (compile in Compile in `session-manager`).toTask.value (compile in Compile in `identity-manager`).toTask.value @@ -53,7 +51,6 @@ compileAll := { } cleanAll := { - (clean in Compile in `frontend-server`).toTask.value (clean in Compile in `token-manager`).toTask.value (clean in Compile in `session-manager`).toTask.value (clean in Compile in `identity-manager`).toTask.value @@ -65,7 +62,6 @@ cleanAll := { runAll := { - (run in Compile in `frontend-server`).evaluated (run in Compile in `token-manager`).evaluated (run in Compile in `session-manager`).evaluated (run in Compile in `identity-manager`).evaluated diff --git a/frontend-server/build.sbt b/frontend-server/build.sbt deleted file mode 100644 index 1e07a36..0000000 --- a/frontend-server/build.sbt +++ /dev/null @@ -1,22 +0,0 @@ -name := "frontend-server" - -version := "1.0" - -scalaVersion := "2.11.5" - -scalacOptions := Seq("-unchecked", "-deprecation", "-encoding", "utf8") - -resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" - -libraryDependencies ++= { - val akkaV = "2.3.10" - val akkaStreamV = "1.0-RC2" - Seq( - "com.typesafe.akka" %% "akka-actor" % akkaV, - "com.typesafe.akka" %% "akka-stream-experimental" % akkaStreamV, - "com.typesafe.akka" %% "akka-http-core-experimental" % akkaStreamV, - "com.typesafe.akka" %% "akka-http-scala-experimental" % akkaStreamV - ) -} - -Revolver.settings diff --git a/frontend-server/project/plugins.sbt b/frontend-server/project/plugins.sbt deleted file mode 100644 index 2a01432..0000000 --- a/frontend-server/project/plugins.sbt +++ /dev/null @@ -1 +0,0 @@ -addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") diff --git a/frontend-server/src/main/resources/application.conf b/frontend-server/src/main/resources/application.conf deleted file mode 100644 index cffad07..0000000 --- a/frontend-server/src/main/resources/application.conf +++ /dev/null @@ -1,12 +0,0 @@ -akka { - loglevel = DEBUG -} - -http { - interface = "0.0.0.0" - port = 8080 -} - -static { - directory = "./frontend-server/web" -} \ No newline at end of file diff --git a/frontend-server/src/main/scala/FrontendServer.scala b/frontend-server/src/main/scala/FrontendServer.scala deleted file mode 100644 index 7c24409..0000000 --- a/frontend-server/src/main/scala/FrontendServer.scala +++ /dev/null @@ -1,18 +0,0 @@ -import akka.actor.ActorSystem -import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Directives._ -import akka.stream.ActorFlowMaterializer -import com.typesafe.config.ConfigFactory - -object FrontendServer extends App { - val config = ConfigFactory.load() - val interface = config.getString("http.interface") - val port = config.getInt("http.port") - val staticDirectory = config.getString("static.directory") - - implicit val actorSystem = ActorSystem() - implicit val materializer = ActorFlowMaterializer() - implicit val dispatcher = actorSystem.dispatcher - - Http().bindAndHandle(interface = interface, port = port, handler = getFromDirectory(staticDirectory)) -} From 97dd6abd7e820dcc7dd5a008d579cabe4bb45bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20G=C5=82odek?= Date: Mon, 11 May 2015 14:01:15 +0200 Subject: [PATCH 3/5] applied reasonable convention to the routes --- .../src/main/scala/AuthCodeCard.scala | 54 ++++++++--------- auth-fb/src/main/scala/AuthFb.scala | 38 ++++++------ .../src/main/scala/AuthPassword.scala | 58 ++++++++++--------- 3 files changed, 78 insertions(+), 72 deletions(-) diff --git a/auth-codecard/src/main/scala/AuthCodeCard.scala b/auth-codecard/src/main/scala/AuthCodeCard.scala index e1c29da..00f095f 100644 --- a/auth-codecard/src/main/scala/AuthCodeCard.scala +++ b/auth-codecard/src/main/scala/AuthCodeCard.scala @@ -28,35 +28,37 @@ object AuthCodeCardCard extends App with JsonProtocols with Config { Http().bindAndHandle(interface = interface, port = port, handler = { logRequestResult("auth-codecard") { - (path("register" / "codecard" ) & pathEndOrSingleSlash & post & optionalHeaderValueByName("Auth-Token")) { (tokenValue) => - complete { - service.register(tokenValue).map[ToResponseMarshallable] { - case Right(response) => Created -> response - case Left(errorMessage) => BadRequest -> errorMessage + pathPrefix("auth-codecard") { + (path("register") & pathEndOrSingleSlash & post & optionalHeaderValueByName("Auth-Token")) { (tokenValue) => + complete { + service.register(tokenValue).map[ToResponseMarshallable] { + case Right(response) => Created -> response + case Left(errorMessage) => BadRequest -> errorMessage + } } - } - } ~ - (path("login" / "codecard" / "activate") & pathEndOrSingleSlash & post & entity(as[ActivateCodeRequest])) { (request) => - complete { - service.activateCode(request).map[ToResponseMarshallable] { - case Right(response) => OK -> response - case Left(errorMessage) => BadRequest -> errorMessage + } ~ + (path("activate") & pathEndOrSingleSlash & post & entity(as[ActivateCodeRequest])) { (request) => + complete { + service.activateCode(request).map[ToResponseMarshallable] { + case Right(response) => OK -> response + case Left(errorMessage) => BadRequest -> errorMessage + } } - } - } ~ - (path("login" / "codecard") & pathEndOrSingleSlash & post & optionalHeaderValueByName("Auth-Token") & entity(as[LoginRequest])) { (tokenValue, request) => - complete { - service.login(request, tokenValue).map[ToResponseMarshallable] { - case Right(response) => Created -> response - case Left(errorMessage) => BadRequest -> errorMessage + } ~ + (path("login") & pathEndOrSingleSlash & post & optionalHeaderValueByName("Auth-Token") & entity(as[LoginRequest])) { (tokenValue, request) => + complete { + service.login(request, tokenValue).map[ToResponseMarshallable] { + case Right(response) => Created -> response + case Left(errorMessage) => BadRequest -> errorMessage + } } - } - } ~ - (path("generate" / "codecard") & pathEndOrSingleSlash & post & optionalHeaderValueByName("Auth-Token") & entity(as[GetCodeCardRequest])) { (tokenValue, request) => - complete { - service.getCodeCard(request, tokenValue).map[ToResponseMarshallable] { - case Right(response) => OK -> response - case Left(errorMessage) => BadRequest -> errorMessage + } ~ + (path("generate") & pathEndOrSingleSlash & post & optionalHeaderValueByName("Auth-Token") & entity(as[GetCodeCardRequest])) { (tokenValue, request) => + complete { + service.getCodeCard(request, tokenValue).map[ToResponseMarshallable] { + case Right(response) => OK -> response + case Left(errorMessage) => BadRequest -> errorMessage + } } } } diff --git a/auth-fb/src/main/scala/AuthFb.scala b/auth-fb/src/main/scala/AuthFb.scala index 89c9c12..9061646 100644 --- a/auth-fb/src/main/scala/AuthFb.scala +++ b/auth-fb/src/main/scala/AuthFb.scala @@ -23,27 +23,29 @@ object AuthFb extends App with JsonProtocols with Config { Http().bindAndHandle(interface = interface, port = port, handler = { logRequestResult("auth-fb") { - (path("register" / "fb") & pathEndOrSingleSlash & post & entity(as[AuthResponse]) & optionalHeaderValueByName("Auth-Token")) { (authResponse, tokenValue) => - complete { - service.register(authResponse, tokenValue) match { - case SuccessT(f) => f.map[ToResponseMarshallable] { - case Right(identity) => Created -> identity - case Left(errorMessage) => BadRequest -> errorMessage + pathPrefix("auth-fb") { + (path("register") & pathEndOrSingleSlash & post & entity(as[AuthResponse]) & optionalHeaderValueByName("Auth-Token")) { (authResponse, tokenValue) => + complete { + service.register(authResponse, tokenValue) match { + case SuccessT(f) => f.map[ToResponseMarshallable] { + case Right(identity) => Created -> identity + case Left(errorMessage) => BadRequest -> errorMessage + } + case FailureT(e: FacebookException) => Unauthorized -> e.getMessage + case _ => InternalServerError } - case FailureT(e: FacebookException) => Unauthorized -> e.getMessage - case _ => InternalServerError } - } - } ~ - (path("login" / "fb") & pathEndOrSingleSlash & post & entity(as[AuthResponse]) & optionalHeaderValueByName("Auth-Token")) { (authResponse, tokenValue) => - complete { - service.login(authResponse, tokenValue) match { - case SuccessT(f) => f.map[ToResponseMarshallable] { - case Right(token) => Created -> token - case Left(errorMessage) => BadRequest -> errorMessage + } ~ + (path("login") & pathEndOrSingleSlash & post & entity(as[AuthResponse]) & optionalHeaderValueByName("Auth-Token")) { (authResponse, tokenValue) => + complete { + service.login(authResponse, tokenValue) match { + case SuccessT(f) => f.map[ToResponseMarshallable] { + case Right(token) => Created -> token + case Left(errorMessage) => BadRequest -> errorMessage + } + case FailureT(e: FacebookException) => Unauthorized -> e.getMessage + case _ => InternalServerError } - case FailureT(e: FacebookException) => Unauthorized -> e.getMessage - case _ => InternalServerError } } } diff --git a/auth-password/src/main/scala/AuthPassword.scala b/auth-password/src/main/scala/AuthPassword.scala index 3242250..a634665 100644 --- a/auth-password/src/main/scala/AuthPassword.scala +++ b/auth-password/src/main/scala/AuthPassword.scala @@ -24,36 +24,38 @@ object AuthPassword extends App with JsonProtocols with Config { Http().bindAndHandle(interface = interface, port = port, handler = { logRequestResult("auth-password") { - path("register" / "password") { - (pathEndOrSingleSlash & post & entity(as[PasswordRegisterRequest]) & optionalHeaderValueByName("Auth-Token")) { - (request, tokenValue) => - complete { - service.register(request, tokenValue).map[ToResponseMarshallable] { - case Right(identity) => Created -> identity - case Left(errorMessage) => BadRequest -> errorMessage - } + pathPrefix("auth-password") { + path("register") { + (pathEndOrSingleSlash & post & entity(as[PasswordRegisterRequest]) & optionalHeaderValueByName("Auth-Token")) { + (request, tokenValue) => + complete { + service.register(request, tokenValue).map[ToResponseMarshallable] { + case Right(identity) => Created -> identity + case Left(errorMessage) => BadRequest -> errorMessage + } + } } - } - } ~ - path("login" / "password") { - (pathEndOrSingleSlash & post & entity(as[PasswordLoginRequest]) & optionalHeaderValueByName("Auth-Token")) { - (request, tokenValue) => - complete { - service.login(request, tokenValue).map[ToResponseMarshallable] { - case Right(token) => Created -> token - case Left(errorMessage) => BadRequest -> errorMessage - } - } - } - } ~ - path("reset" / "password") { - (pathEndOrSingleSlash & post & entity(as[PasswordResetRequest]) & headerValueByName("Auth-Token")) { - (request, tokenValue) => - complete { - service.reset(request, tokenValue).map[ToResponseMarshallable] { - case Right(identity) => OK -> identity - case Left(errorMessage) => BadRequest -> errorMessage + } ~ + path("login") { + (pathEndOrSingleSlash & post & entity(as[PasswordLoginRequest]) & optionalHeaderValueByName("Auth-Token")) { + (request, tokenValue) => + complete { + service.login(request, tokenValue).map[ToResponseMarshallable] { + case Right(token) => Created -> token + case Left(errorMessage) => BadRequest -> errorMessage + } + } } + } ~ + path("reset") { + (pathEndOrSingleSlash & post & entity(as[PasswordResetRequest]) & headerValueByName("Auth-Token")) { + (request, tokenValue) => + complete { + service.reset(request, tokenValue).map[ToResponseMarshallable] { + case Right(identity) => OK -> identity + case Left(errorMessage) => BadRequest -> errorMessage + } + } } } } From 7436934ab6eca13aa33459fd86d686e0d3e72326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20G=C5=82odek?= Date: Mon, 11 May 2015 14:01:34 +0200 Subject: [PATCH 4/5] set default password to "" --- auth-password/src/main/resources/application.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/auth-password/src/main/resources/application.conf b/auth-password/src/main/resources/application.conf index 1b0c6fd..503452f 100644 --- a/auth-password/src/main/resources/application.conf +++ b/auth-password/src/main/resources/application.conf @@ -1,3 +1,4 @@ + akka { loglevel = DEBUG } @@ -10,7 +11,7 @@ http { db { url = "jdbc:postgresql://localhost:5432/auth_password" user = "postgres" - password = "postgres" + password = "" } services { @@ -23,4 +24,5 @@ services { host = "localhost" port = 8010 } -} \ No newline at end of file +} + From 839698508f2ebbeb1ec808a3719a8405092fb61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20G=C5=82odek?= Date: Mon, 25 May 2015 21:48:54 +0200 Subject: [PATCH 5/5] tutorial structure fix --- tutorial/index.html | 98 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/tutorial/index.html b/tutorial/index.html index 87cadc3..c6831b0 100644 --- a/tutorial/index.html +++ b/tutorial/index.html @@ -5,19 +5,33 @@
-

Reactive microservices

+

1. Reactive microservices

Reactive microservices is an activator template completely devoted to microservices architecture. It lets you learn about microservices in general — different patterns, communication protocols and 'tastes' of microservices. All these concepts are demonstrated using Scala, Akka, Play and other tools from Scala ecosystem. For the sake of clarity, we skipped topics related to deployment and operations — that's a great subject for another big activator template.

-

Prerequisites

+

+

+

+
+
+

1.1. Prerequisites

To feel comfortable while playing with this template, make sure you know basics of Akka HTTP which is a cornerstone of this project. We recently released an Akka HTTP activator template that may help you start. At least brief knowledge of Akka remoting, Akka persistence, Akka streams and Play Framework websockets is also highly recommended. Anyway, don't worry — all these technologies (and more!) will be discussed on the way but we won't dig into the details.

- -

Structure

+ +
+
+

1.2. Project build overview

This activator template consists of 10 runnable subprojects — the microservices:

    @@ -49,8 +63,12 @@

    Structure

They uses different communication methods, different databases, and different frameworks.

- -

Setup

+ +
+
+

1.3. Setup instructions

Review the configuration files

Take some time to review application.conf files that are located in resource subdirectory of each microservice.

@@ -91,15 +109,25 @@

Play

; project metrics-collector; run 5001
Everything else should work out of the box. Enjoy!

+ +
-

What are microservices?

+

2. What are microservices?

There's no formal or widespread definition of a microservice. However usually microservices are defined as an software architectural style in which system is composed of multiple services. Those services are small (or at least smaller than in typical, monolith applications), can be independently deployed and they communicate using (lightweight) protocols. Well–defined microservice should be organized around business capabilities or to put it in Domain–Driven Design wording — should encapsulate Bounded Context. Sometimes it is said that microservices are implementation of Single Responsibility Principle in architecture.

-

Opportunities

+ +
+
+

2.1. Opportunities

Applications based on a microservice architecture are basically distributed systems. This means that they can scale horizontally in a much more flexible way than monolithic systems — instead of replicating whole heavyweight process one can spawn multiple instances of services that are under load. This guarantees better hardware utilization — money savings. Another important consequence of moving from monolith to microservices is the need of designing for failure which can result in a truly reactive system. Like it or not, while designing a distributed system you have to take failure into account — otherwise you will see your system falling apart. @@ -109,15 +137,23 @@

Opportunities


Shorter time–to–market stems not only from well–organized teams but also from other features of MSA. First of all, microservices are supposed to be easier to understand (thus maintain) than monolithic systems. Smaller size also means that microservices can be easily rewritten (or simply disposed) instead of being refactored which usually is expansive and results in sub–optimal code quality. Autonomy of teams enables polyglot approach which provides better utilization of tools and bounded context is supposed to increase code reusability. Microservices should also be fun for developers, as everything that's new and challenging.

- -

Dilemmas & problems

+ +
+
+

2.2. Dilemmas and problems

- A shift from monolithic to microservices architecture is a serious step. Distributed systems are totally different than monolithic ones and have their own dilemmas and problems. First and foremost microservices are all about communication and protocols. One should be aware that microservices doesn't magically suppress complexity — they just move it from code to communication layer. Different communication protocols (synchronous and asynchronous) and transport guarantees will be the main subject of this tutorial (see chapters 4, 5, 6, 7). Another important subject worth mentioning while discussing microservices is a polyglot persistence — how to embrace multiple different data stores and not lose consistency and performance. You can check out this approach in our activator template in chapter 3. Very common question asked while developing microservices is 'how big is a microservice?' — we'll discuss it in chapter 3. Microservice approach requires some boilerplate; one may be tempted to share code using shared libraries which introduce coupling — why and when would you like to do that? See chapter 5. There's also a multitude of the problems which are out of the scope of this template like: testing (why, when and how to do it?), polyglot approach (is it worth the cost?), operations, contract management (what's my API, who are my collaborators, how can I contact them?), API versioning (how to stay backward compatible?), logging & debugging and security. Feel encouraged to enrich this activator template with suitable examples and tutorials — and let us know! + A shift from monolithic to microservices architecture is a serious step. Distributed systems are totally different than monolithic ones and have their own dilemmas and problems. First and foremost microservices are all about communication and protocols. One should be aware that microservices doesn't magically suppress complexity — they just move it from code to communication layer. Different communication protocols (synchronous and asynchronous) and transport guarantees will be the main subject of this tutorial (see chapters 3.1., 3.2., 3.3., 3.4.). Another important subject worth mentioning while discussing microservices is a polyglot persistence — how to embrace multiple different data stores and not lose consistency and performance. You can check out this approach in our activator template in chapter 3. Very common question asked while developing microservices is 'how big is a microservice?' — we'll discuss it in chapter 3. Microservice approach requires some boilerplate; one may be tempted to share code using shared libraries which introduce coupling — why and when would you like to do that? See chapter 3.1. There's also a multitude of the problems which are out of the scope of this template like: testing (why, when and how to do it?), polyglot approach (is it worth the cost?), operations, contract management (what's my API, who are my collaborators, how can I contact them?), API versioning (how to stay backward compatible?), logging & debugging and security. Feel encouraged to enrich this activator template with suitable examples and tutorials — and let us know!

+ +
-

System blueprint

+

3. System blueprint

To present different concepts related to microservices we built an authentication system. The idea behind it is really simple — you can sign in/sign up using arbitrarily chosen authentication methods (currently they're email–password, Facebook Oauth, codecard). Number of used authentication methods indicates user's token strength. User that presents a valid authentication token can access business applications behind the authentication system. To test if our authentication system actually works we integrated a simple application that after singing in lets users subscribe and get notifications in real–time about bitcoin market events such as rate change, volume above/below certain level etc. @@ -142,9 +178,17 @@

System design

Spend some time analyzing communication paths, protocols and data stores — it might be useful during our tour around each service of the system.

+ + +
-

Synchronous HTTP — akka–http

+

3.1. Synchronous HTTP — akka–http

Synchronous communication is a data transfer method in which receiving and sending are governed by strict timing signals. It's usually takes the form of a request–response protocol — party sends data only when it's explicitly asked for it. That's how typical HTTP service works. Synchronous protocols are very popular because they're easy to understand and analyze. However they have one significant drawback — they don't scale well. First of all, synchronous protocols introduce liveness issues — when you ask for something you have to be prepared that your call may explicitly fail or even worse you may never get any response at all (that's why you need timeouts). Secondly, usually if you ask for something, you have to wait for the response to continue processing. Several such calls and you'll end up waiting most of the time instead of doing something actually useful. Having said that, world wide web we use daily is mostly synchronous — you click 'Log in' button and you wait for 'Login successful' confirmation box. Request–response — that's how vanilla HTTP works. Synchronous communication is also useful (well, almost unavoidable) while designing microservice–based system — learn why and when.

@@ -182,16 +226,19 @@

auth-fb

token-manager

- token-manager is a focal point of our authentication system. Authentication method services gets a fresh token for logged in users from it and business services verifies tokens presented by users to check identities. token-manager is built using previously presented layered architecture (routes — service — repository) but it misses gateway as it doesn't initiate communication with other services. It's the most important service in whole project so it's equipped with custom metrics reporting — there's processing time reporting for every request and success/failure reporting for each action. You'll learn how it works under the hood in the next chapter. + token-manager is a focal point of our authentication system. Authentication method services gets a fresh token for logged in users from it and business services verifies tokens presented by users to check identities. token-manager is built using previously presented layered architecture (routes — service — repository) but it misses gateway as it doesn't initiate communication with other services. It's the most important service in whole project so it's equipped with custom metrics reporting — there's processing time reporting for every request and success/failure reporting for each action. You'll learn how it works under the hood in the next chapter.

session-manager

In the microservice architecture you usually have services that offer public API accessible by clients and internal API that is being used only by other services. Sometimes service offers some features that are public and others that are strictly private. That's the case with token-manager — you want your clients to be able to log out (delete token) but you don't want them to be able to add new token without going through one of auth services. This could be easily handled by a proxy server (like HAProxy or nginx) but sometimes you may want to do more complex transformations (ex. change API). In this case you should write a proxy service. Session manager is an exemplary proxy service that changes API and hides internals.

+
-

Asynchronous HTTP stream — akka–http

+

3.2. Asynchronous HTTP stream — akka–http

Asynchronous communication, unlike synchronous, is not restricted by any timing signals. Asynchronous communication is usually implemented by message passing (vs request–response) — 'telling' (vs 'asking'). Asynchronous protocols usually scales better as communicating parties don't have to wait. However asynchronous message passing is unnatural, can get really complex thus it's very often complicated and hard to debug. Nonetheless, truly reactive systems should relay on asynchronous message–passing — as stated in Reactive Manifesto.

@@ -222,20 +269,28 @@

metrics-collector

If you want to have a closer look at this schema go here.

+ +
-

Asynchronous HTTP — websockets in Play

+

3.3. Asynchronous HTTP — websockets in Play

Another way to provide asynchronicity in the world of webservers are websockets. Websockets are similar to TCP sockets but additionally they provide minimal framing. This makes them ideal for asynchronous message passing. You've seen them in action in metrics-collector but this time we'll analyse more complex service.

btc-ws

- btc-ws is a façade service for business part of our application. It allows users, after authentication, to manage subscriptions for BTC market events and receive alerts. While one could argue that user's actions can be handled synchronously, market events are asynchronous and should be handled like that. Websockets are perfect fit for such case — otherwise we would have to use HTTP polling which is inefficient. First thing to notice in the btc-ws code is a long block of mappings needed for a websocket message — scala object translations. Websocket initialization code is really straightforward — it retrieves user's identity based on presented token and opens websocket handled by WebSocketHandler actor. You'll learn what happens there in the next chapter. + btc-ws is a façade service for business part of our application. It allows users, after authentication, to manage subscriptions for BTC market events and receive alerts. While one could argue that user's actions can be handled synchronously, market events are asynchronous and should be handled like that. Websockets are perfect fit for such case — otherwise we would have to use HTTP polling which is inefficient. First thing to notice in the btc-ws code is a long block of mappings needed for a websocket message — scala object translations. Websocket initialization code is really straightforward — it retrieves user's identity based on presented token and opens websocket handled by WebSocketHandler actor. You'll learn what happens there in the next chapter.

+ +
-

Asynchronous messaging — Akka actors

+

3.4. Asynchronous messaging — Akka actors

The 'go–to' tool when it comes to asynchronous message passing in Scala world is of course Akka. It has great capabilities and convenient interface but it comes with a cost. If you chose Akka as a communication protocol for a significant part of your project you're losing many of the benefits of polyglot approach. First of all, if you want to interoperate with Akka, you have to be on JVM and preferably use Java or Scala. Your code may also become more tightly coupled — Akka encourages usage of shared libraries and data structures. As a result, in certain cases it might be better to consider using lightweight message queues such as RabbitMQ or Kafka to avoid aforementioned drawbacks but if you're sure you won't be leaving JVM anytime soon, Akka is definitely the best choice. That was also our decision in the fully asynchronous part of our system.

@@ -253,12 +308,17 @@

btc-users

btc-users is a microservice completely based on Akka. It consists of three actors types: UsersManager, UserHandler and DataFetcher. UsersManager plays a role of supervisor. It responds to requests from btc-ws to create a handler (UserHandler actor) for user with given id. UserHandler is where all the heavy lifting happens. It's a persistent actor that processes subscription requests and issues alarms based on ticker from BTC market. First and foremost we once again leveraged the polyglot persistence approach — we used Akka persistence to persist subscription settings. Subscribe/unsubscribe actions are a perfect cases for event sourcing and that's exactly how we implemented it — see receiveCommand and receiveRecover. Besides handling subscribe/unsubscribe requests, UserHandler responses to QuerySubscriptions and broadcasts market alarms. UserHandler actor manages its lifetime similar to how btc-ws does — by heartbeats and timeouts. DataFetcher is a very simple actor that every few seconds fetches BTC ticker and broadcasts it to all UserHandler actors via Akka router. That's it — that's how subscriptions are managed and market alarms are issued.

+ +
-

Summary

+

4. Summary

During our tour of microservices we hopefully showed the full power that comes with mixing different approaches, techniques and tools. Scala's toolbelt (and especially Akka and Play) is, without a question, ready for building reactive microservice–based distributed systems. However before migrating all your project to MSA make sure you deeply understand all dilemmas and problems of microservices, particularly ones we had to omit to keep this activator template concise like: eventual consistency, testing, operations & deployment, contract managing, versioning, monitoring, logging & debugging and security. Good luck, have fun and let us know about your adventures on the microservice way!

+