Sunday 5 July 2020

Spring Microservices with Kubernetes on Google, Ribbon, Feign and Spring Cloud Gateway

In the previous post, we built a JWT authentication server. Now we will build on that server and create an initial instance of the application that I am working on. I have been working on a education engine called Varahamihir. We will talk about this engine at a later date, but for the scope of this post, we will define this engine as below.

The engine consists of an authorization and routing service. We have taken our authorization server from the last post and added the spring cloud gateway routing functionality to that. This will be the only micro-service in our architecture that will be exposed to internet while all other micro-services will be strictly internal services.
The authorization services maintains all the users that exist in the system but these users are provided access to different resources based on their role. When designing micro-services based solutions, people have following two options.
  1. Authorization server performs necessary authentication and authorization and then for communication between micro-services specific client tokens are used.
  2. The authorization token received from the user is passed to each of the micro-services. This requires us to make sure the token is passed around for completing the life-cycle of the request.
I always prefer option 2 for better audit and tracing capabilities. Let's look all the things that we need to make this solution work.

Gateway Changes

As we have already mentioned, we are re-purposing our authorization server to also double up as routing gateway.

As we can see in the yaml file, we define two routes, one for our student-service, and another for our guardian-service. Since we are going to use Kubernetes Service Discovery, these names have to match the service names declared in our pod configuration. Please be careful to only used exposed service names.
In the yaml file above, we see two filters defined. These are the hooks to add/remove something to all the requests that are being handled by Spring Cloud Gateway.



For now these filters are just printing the request headers and are not performing anything useful. We also need to make changes to our SpringApplication to enable discovery for the services.

Look at the annotations on the class. The most important annotation is @EnableDiscoveryClient. Since we have to build a number of micro-services and many of the functions are common, we have created couple of modules which we will include in all micro services. The authentication path for identity-server is different than other micro-services because other micro-services will just validate the JWT  token received from the gateway. 
Spring does not recursively import all the application.properties files from child modules, we have to collect really required properties from any child module and manually import them to the application. That is the reason, we have @PropertySource("classpath:jwt.properties"), in the application because this is the properties file where some required properties are stored in one of the child modules. I would have liked if there was some way to instruct Spring to import all the child module's application.properties files recursively.
Apart from these changes, the gateway application is very similar to what we saw in previous post, we have to just pay attention to list of whitelisted URLs in VarahamihirWebServerSecurityConfig. We have basically whitelisted actuator and user registration URLs.

Micro-services

The gateway service (identity-server) is sitting on the edge and is the only entity exposed to internet. It performs complete authentication and authorization cycle. Once the request is accepted by the gateway and is being passed on the other micro-services, they only verify the token and install a security context so that appropriate role based access control can be applied on the endpoints.

We have following roles defined in our system.

Most of the roles defined in the system are self-explanatory. Some of the roles of interest are as below.
  • ADMIN -- This is like the super-user of the system, will have complete access to all the functions of the system
  • TENANT_ADMIN -- This is like the above role but will only have access to resources of a tenant. This is the admin of a particular tenant
  • REFRESH -- This is an internal role that is assigned to the refresh token issued by the Authorization Server. This role is only allowed to call the refresh endpoint.
  • AUTHENTICATE -- This is the role that is assigned when the user requests a new token using the username and password. 
There are other specific roles for specific types of users and provide them access to their resources.

Authentication

The authentication flow of the microservice starts with few initial filters. Let's look at the order in the security config code.

The code is self-explanatory, we first whitelist a bunch of URLs and options and then we add three filters. The first is our good old TenantHeaderFilter that extracts and installs tenant into the SecurityContext. This is very similar to what we saw in previous post as well.


The next filter is the AuthorizationHeaderFilter, which extracts the Authorization header from the request and installs it into the SecurityContext.

Now the AuthenticationWebFilter provided by Spring is called which triggers the authentication. This filters requires a authentication manager, for which we use a standard UserDetailsRepositoryReactiveAuthenticationManager. We need to provide a UserDetailService to instantiate the authentication manager.

A standard implementation of UserDetailService requires one to fetch username and password from some persistent store. In our scenario, we already have an authenticated token, so we just provide a dummyPassword that is defined in the above class. All the other parameters to construct a proper UseDetails object can be extracted from the existing token and authentication can proceed.
In this flow, we are only worried about decoding and verifying the JWT token. 
The main authentication function is handled by VarahamihirJWTClientAuthWebFilter

This class uses a class VarahamihirJWTClientAuthConverters to perform the actual authentication. Both these classes are below. The filter function in VarahamihirJWTClientAuthWebFilter decided if the authentication needs to be done and then authenticate function performs actual authentication.
We use a utility class VarahamihirJWTBaseUtil. In the previous post, we had talked about VarahamihirJWTUtil. Since then, the class is refactored into two classes where the base class has all the decoding related functions and the child class has additional functionality. Many of the beans that we need to create a token are not needed when we want to validate and decode it.

We use a utility class VarahamihirJWTBaseUtil. In the previous post, we had talked about VarahamihirJWTUtil. Since then, the class is refactored into two classes where the base class has all the decoding related functions and the child class has additional functionality. Many of the beans that we need to create a token are not needed when we want to validate and decode it.

Communication

Since we will be using OpenFeign for communication across micro-services, we define this in this common module as well.
We have three micro-services and we expose the interfaces related to each of them that are required for communication.

This the interface to identity server and provides two services, given a username, it can get you the details of that user and it can create a user. The second is required because when the other micro-services need to create a student or a guardian, they first need to create a user and then create student or guardian. The other micro-services also make sure appropriate roles are applied to users based on their function. The external endpoint doesn't allow one to provide roles.

The student service interface provides two interfaces, one to get the details of a student and another to create a student. This will be called from the guardian endpoint since we envisage that a guardian will create students.

The guardian service just provides a single interface and that allows one to query details of a guardian.
We also need to provide a config for all the Feign clients.


Implementing micro-service

Now that we have the common module completed, we can create our micro-services modules and implement the actual business logic. Here I present one sample of how the micro-service looks like. We take the example of Student micro-service.

We first define an endpoint class, in this class our endpoint has two interfaces, a GET and a POST.
Now we define a service layer that will implement the functionality required by the endpoint.

Next we define a repository.

Next we define a mapper class that can convert entities to Pojo. This is not really required but is useful if what is exposed over JSON is remarkably different than what is stored in database.

Finally, our application. Make sure all the annotations are in place.

Deployment

First thing we need to do is the bundle the application in docker images. We need to make sure our pom.xml has following directive.

The build-image goal in spring-boot-maven-plugin makes sure that on mvn clean package -DskipTests command creates a docker image. We have prefixed the image with gcr.io because we want to use google cloud and if we push that image, it will be available in google container registery. 
Now we need to generate yaml files for each of our applications. Even though I used dekorate to automatically generate yaml files, I always had to manually modify them. So I finally decided to create my own yaml files.
Since I am using Google Cloud SQL, I am using the sidecar approach to deploy the cloud sql proxy.  Since the database requires authentication, I am using the kubernetes secret to define that.
We use Workload Identity method for the application to work in google cloud. Here are the steps that need to be followed for that.
Go to google cloud container page and create a cluster for yourself. You will also need to create a google project before that. Make a note of the cluster name.
Set your google cloud project in your shell and create a service account.
$ gcloud iam service-accounts create my_gsa_name
$ kubectl create namespace my_namespace
$ kubectl create serviceaccount --namespace my_namespace my_ksa
  
Now associate policy bindings and annotate the account
$ gcloud config set project my_project
$ gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:cluster_project.svc.id.goog[my_namespace/my_ksa]" \
  my_gsa_name@gsa_project.iam.gserviceaccount.com
You can leave my_namespace to default if you wish. my_ksa is your kubernetes service account which is tied to your my_gsa_name google account. Since we are going to use Cloud SQL, you need to make sure the my_gsa_name has one of the Cloud roles into it. Otherwise you will get errors related to permissions for using Cloud SQL.
Now we need to complete the binding between KSA and GSA.
$ kubectl annotate serviceaccount \
  --namespace my_namespace \
   my_ksa \
   iam.gke.io/gcp-service-account=my_gsa_name@gsa_project.iam.gserviceaccount.com
You also need to create a secret for database that the yaml file will use to authenticate with cloud sql. This secret is used within the yaml file for service deployment.
$ kubectl create secret generic my-secret \
  --from-literal=username=my-db-user \
  --from-literal=password=MyDbPassword \
  --from-literal=database=my-db-name


Here is the yaml file for one of the micro-services. Similarly one can write for other micro-services as well.

Now that we are ready with all the stuff, we just need to call mvn clean package -DskipTests. This will create all the docker images. Now we need to push these docker images to Google Container Registry. For this, we need to call docker push on each of the images. I use following script to automate the complete deployment operation.
#!/bin/bash
IMAGES=`docker images --format "table {{.Repository}},{{.ID}},{{.Repository}},{{.Tag}},{{.Size}}" | grep 'identity-server\|student\|guardian' |cut -d ',' -f 2`
[ $? -eq 0 ]  || exit 1
docker image rm -f $IMAGES
mvn clean package -DskipTests
[ $? -eq 0 ]  || exit 1
docker push gcr.io/varahamihir-cloud/student
[ $? -eq 0 ]  || exit 1
docker push gcr.io/varahamihir-cloud/guardian
[ $? -eq 0 ]  || exit 1
docker push gcr.io/varahamihir-cloud/identity-server
[ $? -eq 0 ]  || exit 1
kubectl -n service rollout restart --namespace=varahamihir-k8s-ns deployment student
[ $? -eq 0 ]  || exit 1
kubectl -n service rollout restart --namespace=varahamihir-k8s-ns deployment guardian
[ $? -eq 0 ]  || exit 1
kubectl -n service rollout restart --namespace=varahamihir-k8s-ns deployment identity-server
[ $? -eq 0 ]  || exit 1
Now that the servers are deployed, we can check the condition of all the pods.
$ kubectl get pods --namespace=my-namespace
NAME                               READY   STATUS    RESTARTS   AGE
guardian-7c75fcb9c8-txg4q          2/2     Running   0          3h6m
identity-server-67d7878db8-47lqq   2/2     Running   0          3h6m
student-68485fb749-l65gc           2/2     Running   1          3h6m
As we can see, each of the pods are running two workloads because the cloud_sql_proxy is running as a sidecar for each of the pods. The benefit of this is that we can use the cloud sql database as localhost. We can see the logs of any of the processes by following command.

$ kubectl logs --namespace=varahamihir-k8s-ns pods/student-5bf4ccd6cc-5g29q --container student 
Error from server (NotFound): pods "student-5bf4ccd6cc-5g29q" not found
vavasthi@VinayLinux-Desktop:~/work/varahamihir$ kubectl logs --namespace=varahamihir-k8s-ns pods/student-68485fb749-l65gc --container student                         
Container memory limit unset. Configuring JVM for 1G container.
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=216643K -XX:ReservedCodeCacheSize=240M -Xss1M -Xmx524732K (Head Room: 0%, Loaded Class Count: 35835, Thread Count: 50, Total Memory: 1073741824)
Adding 127 container CA certificates to JVM truststore

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)
Now that we are ready, we can see our micro-services in action. First let's create a guardian in the system.
$ curl --request POST --url http://35.225.145.174/default/registration/guardian --header cache-control: no-cache --header content-type: application/json --header x-tenant: default --data {"username" : "testguardian","password" : "testguardian1123","fullname" : "My Guardian","email":"testguardian1@varahamihir.com"}
{
    "tenantId": "738f6863-32dc-4a92-9f3a-cad3ef38fe6f",
    "user": {
        "email": "testguardian1123",
        "grantedAuthorities": [
            "GUARDIAN",
            "USER"
        ],
        "id": "5d6a6e08-0a88-4c5f-9bc1-b220ece79397",
        "mask": 0,
        "tenantId": "738f6863-32dc-4a92-9f3a-cad3ef38fe6f",
        "username": "testguardian"
    },
    "userId": "5d6a6e08-0a88-4c5f-9bc1-b220ece79397"
}
Now we can authenticate using this guardian.
$ curl --request POST \
  --url http://35.225.145.174/default/oauth/token \
  --header 'authorization: Basic c3VwZXJzZWNyZXRjbGllbnQ6c3VwZXJzZWNyZXRjbGllbnQxMjM=' \
  --header 'cache-control: no-cache' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --header 'postman-token: 26951e8d-bbe2-f617-896d-9a31658fe142' \
  --header 'x-tenant: default' \
  --data 'username=testguardian&password=testguardian1123&audience=self&grant_type=password'
  {
    "auth_token": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..6p7meCoT0JyW9dKjeM_h3w.1bjKuj_5nVS_a7tC8zX6euEXyarrrQNCm62ImgrTxCw68oc7leQsAPh5TYyNpyGSE3pdsKCAie67c8WW13dBpRcw22BbitGTKdabpo8L3z5Y51YB7wWx_lQuSfXkTfd9haa2OmnpUjYyNMdHlu6QqGx4fI7iIOC0a4Ir4SkeF6jLMlpxyqpMkPyeZuQZHZDyjT9Cyo8bTFkaNZ8wPy_PX-nCWZROjQiXUQ33ChQ5Uy3QkBz0CCvAOMUjDyIuIA-yH2O35HkCsX44yZ2w-DDjzNyYe5WJIF1iuTpSQfLtkG0jvqkZUp5HyD6wwB5WcvbDzj-wOV7cuVEvY1BoFfUXEjfeZ-pH45_WWV05PeKyiCA.2xBZDLa9VQqiOUGK_pESuA",
    "expiry": 500,
    "refreshToken": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..FttlYRPM4nwViMyZELXnsQ.ZRklt6EClyJCI3lxaIr_W84mtMfdoZUlzWVkM-85tDz8-IwhRjtzieRfErzcuWJB1MB2Y2m4QtVRuLMagilWqc_CbLYb31SOLlMDfHN6TOJck-qpFg_qziaofFOFVY6idlUP9GO2-nuDjROXJLhjFC47mbwOrfqdGoWXAqoaBJzhgKRnKcWJKG_COjN5uXhnirCYM_bc3bI8HJG_1i_Ca_7wgDadSOIyXwS9-DqpqMjMBLZUyIe9S_jqwG7RZox1Ll_MWv7DZvYvhk8ouFZSkB5tL7qI2tNoEPNre_rICfSxNIgJmx_YeI-4IgvQyAbG6SanEDqEfEmRowjbQJ9GCw.-QPY9_YOFllm9yqb-B1WuQ",
    "refreshTokenExpiry": 1000,
    "scope": ",",
    "tokenType": "Bearer"
}
Now using the auth_token, the guardian can add one student to himself.
$ curl --request POST \
  --url http://35.225.145.174/default/guardian/testguardian/student \
  --header 'authorization: Bearer eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..6p7meCoT0JyW9dKjeM_h3w.1bjKuj_5nVS_a7tC8zX6euEXyarrrQNCm62ImgrTxCw68oc7leQsAPh5TYyNpyGSE3pdsKCAie67c8WW13dBpRcw22BbitGTKdabpo8L3z5Y51YB7wWx_lQuSfXkTfd9haa2OmnpUjYyNMdHlu6QqGx4fI7iIOC0a4Ir4SkeF6jLMlpxyqpMkPyeZuQZHZDyjT9Cyo8bTFkaNZ8wPy_PX-nCWZROjQiXUQ33ChQ5Uy3QkBz0CCvAOMUjDyIuIA-yH2O35HkCsX44yZ2w-DDjzNyYe5WJIF1iuTpSQfLtkG0jvqkZUp5HyD6wwB5WcvbDzj-wOV7cuVEvY1BoFfUXEjfeZ-pH45_WWV05PeKyiCA.2xBZDLa9VQqiOUGK_pESuA' \
  --header 'cache-control: no-cache' \
  --header 'content-type: application/json' \
  --header 'postman-token: 2e9901da-ef01-a3bb-ba28-1f0dff5f7381' \
  --header 'x-tenant: default' \
  --data '{\n	"username" : "teststudent1",\n	"password" : "teststudent123",\n	"fullname" : "Test Student",\n	"email":"teststudent@varahamihir.com",\n	"guardianName":"testguardian"\n}'
      "userId": "45fe4888-2a64-48bd-bea7-ad512f0ef724",
    "tenantId": "738f6863-32dc-4a92-9f3a-cad3ef38fe6f",
    "guardianId": "5d6a6e08-0a88-4c5f-9bc1-b220ece79397",
    "user": {
        "id": "45fe4888-2a64-48bd-bea7-ad512f0ef724",
        "tenantId": "738f6863-32dc-4a92-9f3a-cad3ef38fe6f",
        "fullname": "Test Student",
        "username": "teststudent1",
        "email": "teststudent@varahamihir.com",
        "mask": 0,
        "grantedAuthorities": [
            "STUDENT",
            "USER"
        ]
    }
}
  
So we can see the system coordinating across three micro-services. The communication can also be effective since the communication is happening across local ip addresses and doesn't have to go across the external load balancer. Each of the micro-services are also not aware of the ip addresses of each of the pieces and Kubernetes Discovery Service takes care of it.
Complete code synchronized with this blog post is available in my repository tagged as v1.1.


Wednesday 1 July 2020

Building a JWT Authorization Server

I have playing with the idea of Spring Cloud Gateway, micro-services and deploying the service on Google Cloud. As I was ready to deploy the initial version on google cloud server. I saw following error on the logs of the server that I had built.
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.
Then I started on a path of discovery and following message in a spring security documentation kind of crushed my hopes. 
Spring Security’s Authorization Server support was never a good fit. An Authorization Server requires a library to build a product. Spring Security, being a framework, is not in the business of building libraries or products. For example, we don’t have a JWT library, but instead we make Nimbus easy to use. And we don’t maintain our own SAML IdP, CAS or LDAP products.
So, as they say, the writing was on the wall. I was using Authorization Server provided by Spring and they have stopped supporting that in spring security 5, the spring cloud needed dependency on WebFlux which was supported with spring security 5. All in all I got totally confused with the dependencies and concluded that I will have to build a Authorization Server from scratch. This post is about my effort towards that and some learnings on the way.

First step, I cleaned up all the code that had anything to do with Filters, Authentication, Converters in the old server. WebFlux just doesn't play nicely with any of that. Then I read a primer on reactive programming, I think if you read this blog and look at my code, you will realize that from reactive programming point of view, I am still not very fluent. In any case, I learnt enough to make this work.

The next challenge was database. Any of the relational databases are not reactive and I was stuck with MySQL. For the time being I have left it at that. In future, I will replace it with a backend that is reactive. For now, the interface to persistence layer, the repositories etc are all old style JpaRepository.

Let's get started now. The first step is to look at the primary filter chain. This is equivalent to the WebSecurityConfigurerAdapter in any old style non-WebFlux system.

Let's look at all important method securityWebFilterChain, We first define a AuthenticationWebFilter. This class is provided by Spring and we just use it. But this class requires either an implementation of ReactiveAuthenticationManager or an implementation of ReactiveAuthenticationManagerResolver.  Since we might need more than authentication server because we are building a authorization server that can handle JWT tokens, we decided to implement a ReactiveAuthenticationManagerResolver that can handle two AuthenticationManager based on specified criteria.

As is clear from the resolve method of the class VarahamihirAuthenticationManagerResolver, we have two different ReactiveAuthenticationManager instances, one that will handle Client tokens and the other one that will handle User tokens. To differentiate between the types of tokens, the class UserDetailsRepositoryReactiveAuthenticationManager that is provided by Spring is instantiated with either a VarahamihirReactiveClientUserDetailService which implements ReactiveUserDetailsService for clients or VarahamihirReactiveUserDetailService which implements the same service for normal users. This point, line 58 in VarahamihirWebServerSecurityConfig will make sure that right table from the database is looked up for the identification of client or user. Let's take a look at two ReactiveUserDetailsService classes.


Now we can carefully examine VarahamihirWebServerSecurityConfig which defines the Filter chain for authentication purposes.
  • Lines 56 to 59 define the AuthenticationWebFilter which is added in the chain at line line 68. 
  • Lines 61 to 63 added some filters that I used to extract HTTP parameters into RequestContext so that other methods can have access to them if required. Lots of multi-tenancy code is built around using these variables. For example a path of the URL contains tenant discriminator and it used to identify the tenant. If you want to know the details of it, it is described in detail in my other blog at this location.
  • Lines 65 and 66 define the URLs that need to be exempted from authentication mechanism. For example we want new user registration to be outside the authentication flow.
  • Lines 67 to 71 define actual authenticated endpoint handling.
  • Line 72 adds a filter that will do processing related to JWT tokens.
We also define another AuthenticationManager to take care authentication using JWT tokens. Here is the class. Here is a very critical things to note. Once you are confirming authentication success, you should only use one constructor in UsernamePasswordAuthenticationToken class that takes a set of GrantedAuthority as an argument. Otherwise the authentication will always fail. You can not use setAuthentication(true) as an option. 

This AuthenticationManager is used from the WebFilter that processing all JWT tokens. Here is the filter code.


The filter is making sure authentication is handled and security context is setup properly. There is a hook provided for any processing that one might want to do on authentication success. We have set it to the method onAuthSuccess. We use VarahamihirJWTAuthConverters to convert HTTP headers into a valid authentication object.  Look at apply method below.

We use Nimbus JOSE+JWT for handling of JWT token itself. The functionality of this library is wrapped around our utility class VarahamihirJWTUtil.

These are the most important bits and your authorization server is ready. The complete code is available on my repository here.  The key modules to look for are identity-server and varahamihir-common. This has a working authorization server tagged as v1.0.
$ curl --request POST \
  --url http://localhost:8081/default/registration/user \
  --header 'authorization: Basic c3VwZXJzZWNyZXRjbGllbnQ6c3VwZXJzZWNyZXRjbGllbnQxMjM=' \
  --header 'cache-control: no-cache' \
  --header 'content-type: application/json' \
  --header 'postman-token: 5790c497-8264-f5b3-025f-d9963c5b41a1' \
  --header 'x-tenant: default' \
  --data '{\n	"username" : "admin2",\n	"password" : "admin2123",\n	"fullname" : "Adminitrator for a tenant"\n}'

{
    "id": "9af7a442-bc25-4d2c-b5d1-341dcf6fe2da",
    "tenantId": "a5c8cbe0-bce5-440b-b18e-ffc96d253092",
    "fullname": "Adminitrator for a tenant",
    "username": "admin2",
    "mask": 0
}
$ curl --request POST --url http://localhost:8081/default/oauth/token --header authorization: Basic c3VwZXJzZWNyZXRjbGllbnQ6c3VwZXJzZWNyZXRjbGllbnQxMjM= --header cache-control: no-cache --header content-type: application/x-www-form-urlencoded --header postman-token: 6d5facf2-3143-62ea-09d5-86ee08a76310 --header x-tenant: default --data username=admin2&password=admin2123&audience=self&grant_type=password
{ "auth_token": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..hhAq6K4M2dH4StukQvbH-A.pLREINAeNzvrDuFC03kewv47nVU2_2xqLypkdm_aO2FBWCPDnQNVCva65K1PcMGXpHGZ2hurbex4ELcM4WB9GX93rKn1W9bP1PmWCqPORX43kEPUgV6E9P6GZ0Y0mrvhQcss8M9qOF1gBlTOAD7F8hFEPEE0HQypVGXv1prt-11bSylVJO55-sOHGW-MITaYy8t0eWL8WDdP9msVdrcpjjqEVAaM6snJGPc7xp3l4NM.XUedILH4QfRqRKGliAtgtQ", "expiry": 500, "refreshToken": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..zuO7tVknKsgnM5R37gobRg.TDciaBT-StLDTLjBIeR-rLQgFCG7UOUxHREJ5uK8DgVgCvsNQ4xufTEYxcrUxskF5Gr717-HvIrNsXtTnPQdTpktBfdszNUv8-WFsnG1EV63-t8ZJdo7fWQ_O3DNQY78FvGvvqJoQPs2Besz1P9uDNc4NIb4ivm9Nz51aYMSZYOA-8HzDGlS-0fzQfg5ElUEAjyp0p4KVhpUFifNmZkESGcKfU6tYCbhxGVH1NmD8Ec.ESgaKVnFngNzpYAGDyfq1g", "refreshTokenExpiry": 1000, "scope": "read,write", "tokenType": "Bearer" }

Tuesday 18 June 2019

How to analyse architectures

The very point of a documented architecture is that it will tell you important properties of the system even if the system is still not constructed. While creating architectures, we make decisions that will have an impact on other systems with which our system will interact. The architecture created, as a result of these decisions, needs to be analyzed to make sure it achieves what it intended to achieve.
Since the architecture creation itself might is an iterative process, an analysis of the decisions made can be carried out at any step to make sure quality evaluation is done as early as possible in the lifecycle.
The benefits of architecture evaluation are multiple.

  • Financial There are significant financial savings to be had by evaluating the architecture early in the life cycle my reducing rework at later phases.
  • Forced Preparation During architecture construction, team members are engaged in discussions about multiple alternatives. Many of these discussions are, unfortunately, never documented. A formal architecture evaluation or forces the formal documentation of these discussions and makes sure nothing falls through the cracks.
  • Documenting Rationale An architecture evaluation also forces team members to document the rationale of their choices which is otherwise not documented in many cases.
  • Early detection of problems An architecture evaluation with different perspectives will provide early visibility in product capabilities and limitations.
There are multiple systematic methods for architecture evaluation.  In this post, we will talk about two of the most commonly used methods, Architecture Tradeoff Analysis Method (ATAM) and Cost Benefit Analysis Method(CBAM).
Architecture Tradeoff Analysis Method
This method requires the participation of the following stakeholders.
  1. The Evaluation Team This groups is external to the project team. The teams are 3-5 members and each member is assigned a specific perspective(s). For software systems with a long life cycle, one may keep a standing team for all their evaluations.
  2. Project Decision Makers These people represent the project team. Project Managers, Customer, and Architect are part of this group.
  3. Stakeholders These are stakeholders whose ability to do the job hinges on the quality of architecture. These would include developers, testers, integrators, maintainers, etc.
An architecture evaluation using the above method will produce the following artifacts.
  • Concise presentation of architecture
  • Articulation of business goals
  • Quality requirements in terms of collection of scenario 
  • Mapping of architectural decisions to quality requirements
  • Set of identified sensitivity and tradeoff points
  • Set of risks and non-risks
  • Set of risk themes.
The evaluation itself is conducted in the following phases.
  • Partnership and preparation -- Everybody meets informally and works out the details of the exercise
  • Evaluation -- This phase involves information gathering followed by the analysis of information gathered. The information gathering is performed by the evaluation team and the analysis is performed by all the people including decision makers and stakeholders. This phase consists of the following activity that is required to be completed as part of this phase.
    • Present and explain the process
    • Present business drivers
    • Present architecture
    • Identify architectural approaches
    • Generate quality attribute utility tree
    • Analyze architectural approaches
    • Brainstorm and prioritize scenarios
    • Analyze architectural approaches
    • Compile and present results
  • Follow-up -- This phase involves the generation of the evaluation report by the evaluation team. The essence of this phase is self-examination and improvement. It is followed by a post-mortem which articulates what worked well and what didn't.
Following outputs are generated at the end of the evaluation process.
  • Prioritized statement of quality attribute requirements
  • Catalog of architectural approaches used
  • Approach and quality attribute specific analysis questions
  • Mapping of architectural approaches to quality attributes
  • Risks and Non-Risks
  • Sensitivity and tradeoff points
Cost-Benefit Analysis Method
This method of architectural analysis provides for economic decision making. This approach is not necessarily an independent approach. It builds on the previous approach.
We begin by considering the collection of scenarios generated as part of ATAM or specifically for this exercise. Each of these scenarios is examined to find how they differ in their utility. The utility of the scenarios is determined by looking at variation in scenarios, priorities of scenarios and architectural strategies. The benefit function of an architectural strategy can be computed by the sum of products of all the utility functions and importance of various scenarios.


The ROI can be computed for each architectural strategy by the following method.

As we see, both the methods provide us mechanisms that we can use to evaluate any architecture for its quality. The benefit of a formal architecture evaluation during a project life cycle is always beneficial for the overall life of the project.

Tuesday 9 April 2019

CAP theorem as an architectural tool

When we are writing an application that needs to scale on the cloud, getting an architecture in place that can horizontally scale becomes extremely important.  Let's look at one example. The diagram below captures a very early draft of our product's top-level use case diagram.

The diagram consists of five different actors and six different top-level use cases.  Here we provide a short explanation for each of these.
  1. Authentication And Authorization -- This use case captures the capability of authenticating a user and authorizing it for appropriate access levels in the application. 
  2. Admission -- This use case captures the processes associated with the admission of students within an institution. 
  3. Profile -- This use case captures the profile capability of the system. Different actors might have profiles which look very different.
  4. Lesson -- This use case captures the lesson capability of the system. It consists of the management of lesson plan for classes by faculty and interfaces to make these available to students and parents. It will also provide a mechanism to track the completion of a lesson plan.
  5. Standard -- This use case captures the abstraction of the standard. It will consist of different standards in school, different section, a combination of subjects that are offered, tracking of students in each of the section, etc.
  6. Evaluation -- This use case captures the expected functionality related to an evaluation of the student. It will track things like assignments, examinations, test papers, submissions, grading of these, etc.
Now we look at five different actors of the system.
  1. Student -- This actor represents a student in the system. As a student, he will be authorized to play Student role in the system and will be able to perform operations that he is authorized
  2. TenantAdmin -- Our system is a multi-tenanted system. Each education institute will become a tenant in the system and a TenantAdmin role will have the authorization to perform all the administrative functions related to a tenant.
  3. Faculty -- A faculty user in the system will have access to school-related activities of the student.
  4. Guardian -- A parent user will be authorized with Guardian role and will have access to complete set of activities of Student for which (s)he is a guardian.
  5. SuperAdmin -- This is a user with unrestricted access to perform a variety of housekeeping operations across multiple tenants.
With the above example top-level use case, let's look at what type of data storage requirements we might have. Following is one of the examples of how we can define the database requirements of our system.
  1. Authentication Database Database to store authentication information for all user. This will mandatorily have a field that will define the roles that a user is authorized for.
  2. Admission Database A database that will maintain assets related to the admission process.
  3. Profile Database A database that will store profile information related to a user.
  4. Lesson Database A database that will store lessons
  5. Standard Database A database that will store different standards, sections, and mapping of students to each of these for a particular year
  6. Evaluation Database A database that will store assets related to the evaluation of students.
With the databases identified as above, now we look at how we can analyze them based on their requirements with respect to CAP theorem.
CAP Theorem
  1. Authentication Database This database requires data to be consistent since the password and authentication tokens will be part of this. Availability is also an important requirement of this database. This database doesn't need to be partition tolerant since it will be split across multiple tenants and we may follow a scheme of manual sharding. So an appropriate solution to this might be a C-A database. As we can see in the picture to the right, any relational database would be a good solution for this data store. Since most of the authentication data doesn't change very often, we will frontend the RDBMS with a cache, probably Redis.
  2. Admission Database This data store would contain Parent registration, Admission Form, Comments on Admission Form, Call Letters to parents. None of this information falls into the category that requires ACID capabilities. Since we are building a multi-tenant system, information like parent registration might be shared across multiple tenants. The primary requirements of this data store are Availability so either of the A-P or C-A data stores might be good solutions. Depending on our scale requirements, we can choose Dynamo/Cassandra/Couchbase or any RDBMS.
  3. Profile Database This data store would contain profile information for all the users in all the roles. Since the total number of students across all the tenants might be a very large number, partition tolerance becomes an important factor here. We can live with Eventual Consistency in case of profile information. Any product in the A-P axis would be a good solution for this.
  4. Lesson, Standard, Evaluation Database This data store is on a per-tenant basis and only requirements in this are availability so either of the A-P or C-A data stores might be good solutions. Depending on our scale requirements, we can choose Dynamo/Cassandra/Couchbase or any RDBMS.
As shown by the analysis above, depending on the need for scale, we can have either of the following two data store configurations.
  • MySQL with Redis frontend for authentication and Couchbase for everything else if a large number of users
  • MySQL with Redis frontend for everything for a moderate number of users.
As we have shown above, keeping CAP theorem in mind while analyzing the architecture provides us helpful insights into the choice of products that we intend to use for data storage.

Tuesday 5 March 2019

Why science engine

The thought behind this project is to build a science engine for education purposes. We are completely different from things like Wolfram Alpha because we will not focus on the end result. We want to focus on the process of computation so that it has learning value for students.
Over the years there is an increase in the usage of technology in education. Unfortunately, the usage of technology has not changed the basic structure of how education is imparted. For example, there is a significant move towards e-books but they are just digitized version of paper books. Technology is not really leveraged to make consumption of education more effective.
Replacing blackboards with PowerPoint and books with iPads is not really effective use of technology in imparting education. We inherently believe that education is a social process and there is more to be gained by students interaction among themselves.
Here is what we are planning on building as part of our platform.

  • How to present better content e.g. VideoScribe, Simulation models
  • Matching of content based on the context
  • A virtual model of scientific concepts
  • How students interact with each other and flow of information across them, building knowledge graphs
  • Auto Problem Generators and Auto Problem Solvers
Please stay tuned on more development on this front. We will keep on posting updates here.

Spring Microservices with Kubernetes on Google, Ribbon, Feign and Spring Cloud Gateway

In the previous post, we built a JWT authentication server. Now we will build on that server and create an initial instance of the applicat...