Guided Retirement tool tech stack at a glance

January 07, 2020 by Kawika Avilla

Guided Retirement tool tech stack at a glance by Advicent

About the author

Kawika Avilla

Software developer

Kawika Avilla is a software developer at Advicent, the financial planning technology provider of choice for nearly 100,000 financial professionals.

I had been working on the Guided Retirement tool for a while prior to its launch in December, and while it is a simple tool from the user’s perspective, oh boy, the stack that backs it is elegant.

We set a goal of leveraging modern frameworks and fundamental innovations to enhance the financial planning experience in NaviPlan and every day I see us get closer to that goal. In this article, I aim to give an overview of the stack that supports the new tool, and hopefully, in the future I can get more in-depth on the design and other components that were built or used in our pursuit of using the latest and greatest to innovate.

Oh my goodness, I will do my sincere best to do this justice.
TechStackMap.png

That’s it, there you go. End of the blog. Not really, that is a very broad overview of the tech stack and it is only just the tip of the iceberg.

Let’s break down the sections to provide a little more insight into what is happening underneath the hood.

Westron, or the ‘Common Speech’

So, let’s give some loose definitions so that you know what I’m talking about when I reference it in the future. First things first, Tarheel. Tarheel is Advicent’s proprietary server-side rendering framework that the NaviPlan UI is built on top of. It is responsible for:

Building the user interface that is rendered in the browser

Facilitating communication between the client-side browser and the server-side engine utilizing page requests and AJAX requests

The lore would say that it was implemented by someone who really liked the University of North Carolina, but history became legend, legend became myth, and its origin passed out of knowledge (not actually though).

When I talk about the default NaviPlan UI view, I will be referencing it as the Tarheel UI. The new front-end of the Guided Retirement tool is and was developed in Angular 7 (an SPA framework developed by Google) so the team I worked on uses a lot of TypeScript! TypeScript is a typed superset of JavaScript, so cheers to static typing. We try to keep it up to date with the latest version of the framework as we develop. When I talk about the UI portion of the new application, I will reference it as the Angular UI.

Finally, NPO will proxy requests and responses to and from our award-winning NaviPlan engines, which is the thing interacting and mutating the retirement plan.

“Share the load…the load”

Like most companies, all our services and applications are hosted on clusters to ensure we are highly available for our customers in case of fail-overs or updates. When you ping our online application, it hits the load balancer, so it needs to know where it is directing your requests. The global traffic manager, f5, shoots to the active node of the application.

On top of already being replicated on multiple stacks, the new Guided Retirement tool is built out and hosted based on the user’s locale and language. This means if you check where the files are hosted for the project you would find a folder with separate American English, Canadian English, and Canadian French applications. Pretty cool, eh?

Personalize healthcare planning decisions with NaviPlan.

Learn more

 

Launching into the Guided Retirement tool

It was July 16, 1969 when the Apollo 11 launched on its voyage to the moon and it took 76 hours for the Lunar Module “Eagle” to land. This has nothing to do with launching from NaviPlan to the new Guided Retirement tool but they both contain a form of the word “launch” so it helps me segue into this section without you even noticing. Perfect.

This section (shown in the figure below) will focus on launching from the Tarheel UI to the Angular UI, and all the steps that are performed before the user can use the new tool.

To get to the launch point, the user must go through the client or plan creation wizard and select the Guided Retirement option through Tarheel. This kicks off an AJAX request to NPO to create a plan with specific plan default values. Once the plan creation request succeeds, we access the necessary information for the new plan and store it in your local browser memory to be used in the application; information such as Plan ID and Session ID.

The Session ID is related to your NPO session that allows HTTP clients to work within the same session and impact the same plan.

While launching we also get the authentication token, to ensure that any requests that come in have the proper permissions and to manage scope for these requests. We use JSON Web Tokens (JWT) to encode client claims and sign them with a valid signature that we set. We use them as OAuth 2.0 Bearer tokens and we utilize IdentityServer4 to help us with our authentication service. By the way, when I talk about client in this section, I do not mean an advisor’s client, it is the application that is attempting to get access to the user's account.

So, can you guess what we call our authentication service? Auth Server. Expectations subverted, am I right? For the Guided Retirement tool, we approached authentication by using the Resource Owner Password Grant flow, where it allows our tool to hook into the NPO application’s authentication client to show that its scope permissions include the shiny and new Planning API. To rephrase that, the API will receive a request with a token value attached to the request header and it will decode that token to ensure that the producer of that request has access to use the API. The Planning API is the new service that allows access to all plans, not just published plans, which means we can mutate a financial plan in ways that we were not able to before without going into old technologies. What does that mean? Scalability, performance, and innovative frameworks! The Planning API interacts with the Auth Server to get the application's signing key information so the API will be able to decode a valid token.

Both services are built out in .NET Core. As you can tell, this project has been including a lot of shiny new frameworks. This is great because now we have stateless APIs in the sense that if you have a valid auth token and session ID, you can work concurrently on the same plan and hit the same endpoints.

This is the overview workflow of the authentication process when launching from Tarheel to the Angular application.

Now how does the Planning API get the auth token from the Angular UI and validate it?

Within the Angular application, we have an interceptor that captures all requests and decorates the header with the token. Then, the Planning API pulls the token (and the session ID) off the header of the request and verifies that the payload has a valid signature, has access to use the Planning API and that the token is not an expired, along with other information. It can read this token because it has the private key. If all is valid, the Planning API will then give up the data or manipulate the plan per a request.

planningapi.png

We also append other useful information like your current locale for Canadian and U.S. users.

The best analogy I have for this is a story from my childhood. When I was just a wee lad, my friends and I would build snow forts during recess. If you wanted to access to my fort, La Forte De Rocky, you had to provide a code to our gatekeeper. This works in a similar, far more grown-up and technical fashion where the code is an encrypted and valid token.

An important security feature of access tokens is that they expire. If the Planning API reads an expired token, it will respond with a 401 response, so we had to create a refresh strategy for our current implementation of token-based authentication. We utilize the same interceptor to capture a 401 response and to retry at least once to get a new token if you still have a valid session. We send a request to Tarheel so it can renew the auth token by following this path:

requesttoken.png

As you can see, once the HTTP interceptor requests another token it goes to Tarheel and then Tarheel runs the previous steps as in the diagram before. In this process though, it is checking if your NPO session is still active. If it is, then it will update your session to keep it alive and give you a new token from the auth server. If it is not, then your session has expired, and you no longer have access to the application. This kicks you out to the session expired page.

That is the HTTP interceptor in a nutshell. But since we are in the neighborhood, let’s dive into the rest of the Angular application.

Angular

As I write this, I feel I will do the Angular application a disservice if I do not deep dive into what we did. It merits future blogs since there are a lot of cool things you can do in Angular. For now, I will provide you with an overview to fuel your thirst for knowledge about our tech stack and will indulge you in the future with a Guided Retirement tool Angular deep dive.

The Guided Retirement tool was built in Angular 7, utilizing Typescript (a superset of ES6), which provided us static typing and generics. The Guided Retirement tool was built out as a single-page application, so when you click on a button, you are no longer loading a completely new page. Instead, we are making asynchronous calls for users to modify their data.

We use asynchronous programming that involves data streams and the propagation of data change, which is defined as reactive programming. This includes the use of observables compared to the use of promises (if you are curious about the difference click here).

In short, promises handle a single event when an asynchronous callback occurs, whereas observables are data streams and allow us to pass zero or more events where the callback is called for each event.

RxJS is a library that implements the observer paradigm, in which it helps by manipulating and processing observables in a variety of ways to makes our lives easier while building an asynchronous application. If you are using observables, here is your bible: https://www.learnrxjs.io/.

If you are reading this for the Angular information, then you know about RxJS. However, there is a special type of observable that is called a subject. A great metaphor on subjects was given on the RxJS website here, but I think I will give my own metaphor. In Lord of the Rings, there exists palantíri which allow for one-to-one communication between each palantír in Middle-earth.

In the movie, Sauron used a palantír to communicate with Saruman and provide him messages like to build him an army “worthy of Mordor.” As you can see, Sauron and Saruman communicate directly just like how your normal observable would communicate with a component or service that subscribes to it.

Then Saruman built the army numbering in the tens of thousands and he needed to give his grand speech in the Two Towers to march to Helm’s Deep. To communicate with Sauron’s army, he would need the help of his staff to project his voice and for the orcs to hear him. This is what a subject is in RxJS; it is a one-to-many method of delivering data. Anything that is subscribed to the subject will get the latest information. This is called multicasting.

The Guided Retirement tool uses behavior subjects. A behavior subject is a specific type of subject and does its multicasting in a way that emits the current value to all subscribers.

Here is an example of the plan data service:


export class PlansService {
  private _plan: BehaviorSubject = new BehaviorSubject(null);
  public readonly plan$: Observable = this._plan.asObservable().pipe(filter(resp => resp !== null));

  constructor(private plansApi: PlansApiService) {
    if (localStorage.getItem('planId') !== null) {
      this.getPlan()
        .pipe(take(1))
        .subscribe();
    }
  }

  getPlan(): Observable {
    return this.plansApi.getPlan(localStorage.getItem('planId')).pipe(
      tap(resp => {
        this._plan.next(resp);
      })
    );
  }
}

The plan data service manages any data related to the plan and it allows any component within the application to access plan information (e.g. plan creation date). When the service is initialized, it will initialize the behavior subject with a null value and call getPlan() which is an observable. Note that if you do not subscribe to the observable, it will not execute any call. So, since we subscribe to it, it will call the API service that we set up which makes a GET call to the Planning API. On the response from the API, it will set the next value of the behavior subject as that response. Now any component that subscribes to plan$ will get the plan information without making the GET call.

Forgive me, in the previous explanation I introduced two new concepts: data service and API service. I previously mocked it up as you can see in this image:

planningapi-(1).png

In the Angular code, once the data services and API services get invoked it lives for the entire lifetime of the application. The code snippet above lives in the data service, the data service is responsible for storing and mutating data for UI components to use while the API service is responsible for sending out HTTP requests to the new Planning API.

In the Planning API there exists a “definitions” call on every endpoint. For example, the accounts endpoint in the image below:

apiendpointdefinitions.png

This is a nice and nifty endpoint to get all the valid values and types related to the current plan, which can map to a dictionary type so we can make our application as generic and flexible as possible. Now, let’s compare the definition responses from a single client’s plan versus a joint clients' plan.


// Single Client
{
  "data": {
    "items": [
      {
        "typeName": "401k",
        "fields": [
          {
            "fieldName": "owner",
            "fieldValues": [
              {
                "fieldName": "memberType",
                "validValues": [
                  "Client"
                ]
              },
              {
                "fieldName": "memberId",
                "validValues": [
                  "1"
                ]
              }
            ]
          },
          {
            "fieldName": "marketValue",
            "readOnly": true
          }
        ]
      }
    ]
  }
}


// Joint Client
{
  "data": {
    "items": [
      {
        "typeName": "401k",
        "fields": [
          {
            "fieldName": "owner",
            "fieldValues": [
              {
                "fieldName": "memberType",
                "validValues": [
                  "Client",
                  "CoClient"
                ]
              },
              {
                "fieldName": "memberId",
                "validValues": [
                  "1",
                  "2"
                ]
              }
            ]
          }
          {
            "fieldName": "marketValue",
            "readOnly": true
          }
        ]
      }
    ]
  }
}

You can see that the owner of the account in a single client plan can be the only client while the owner in a joint client plan can be either the client or the co-client. You can also see the market value field is read-only because the holdings update the market value of the account, which you can adjust from another endpoint.

My hope is not to undermine what we did within the Angular application nor the technologies we used. As I said before, this is just a brief overview of the tech stack and further blogs will explore stuff deeper like RxJS and observables.

A wrap to this

Hopefully, this gave you some insight into what it took to build the new Guided Retirement tool. It is very brief for how much work was involved and how many teams it took to innovate upon the NaviPlan financial engines but should bring you up to speed.

Thanks for reading!

Acknowledgments

I would like to give a special thanks to the following colleagues for proofreading and providing advice to me while writing this blog:

Ryan Leung

Joseph Wheaton

Caroline Bank

For more information on the NaviPlan Guided Retirement tool, click here >