Stormpath provides several language-specific SDKs to allow simple interaction with its REST API. The Java SDK is one of our most popular ones. In this article, we’ll dive under the hood and take a closer look at the architecture of the Java SDK. First of all, you might wonder, why did you develop an SDK?
Well, that’s simple. We absolutely believe that our ecosystem of SDKs and integrations makes developer’s lives easier, and that’s our mission. We started Stormpath because auth is tough if you are a security expert, and can be impossible to get right if you’re not. Fast forward three years and now Stormpath offers 13 different SDKs.
The SDKs have been extremely popular among developers. The reason is simple, they can add just a few lines of code and be integrated with Stormpath in minutes!
From REST to Java
The Java SDK does its best to keep developers in mind. As developers of the SDK, this isn’t too difficult since we’re developers too! The Core Java SDK is largely a wrapper around the REST API, which can also be used if you want to experiment from the command line, or write your own SDK.
TIP: Les Hazlewood wrote a blog post about how we migrated our backend to Spring Boot in 3 weeks.
To create a user account with the REST API, you can use curl:
curl --request POST \ --user $SP_API_KEY_ID:$SP_API_KEY_SECRET \ --header 'content-type: application/json' \ --url "https://api.stormpath.com/v1/applications/1gk4Dxzi6o4PbdlEXampLE/accounts" --data '{ "givenName": "Joe", "surname": "Stormtrooper", "username": "tk421", "email": "tk421@stormpath.com", "password":"Changeme1" }'
Using the Java SDK to complete this same task might look more familiar to Java developers:
//Create the account object Account account = client.instantiate(Account.class); //Set the account properties account.setGivenName("Joe") .setSurname("Stormtrooper") .setUsername("tk421") //optional, defaults to email if unset .setEmail("tk421@stormpath.com") .setPassword("Changeme1"); //Create the account using the existing Application object account = application.createAccount(account);
API vs Implementation
The Java SDK defines clear lines between its API and implementation. The project on GitHub shows these are both top-level directories.
The api
module contains the interfaces that developers will interact with. The project uses semantic versioning, which means these classes will not add or remove methods between patch releases. We are at liberty to add classes and methods between minor and major releases.
The impl
module is a little more fluid in that we’re allowed to change things between minor releases, as long as we maintain backwards compatibility. The classes in this module aren’t exposed to end users as much and we caution developers not to cast and use implementation classes.
You can think about the API as a JDBC library. When you write Java code to interact with a database you will be writing generic JDBC statements. During development time, those statements are not tied to any particular DB at all. Later on, at runtime, a specific concrete DB library will be used by JDBC in order to interact with the concrete DB. You wrote (JDBC) code which allows you to switch between different DBs without (ideally) affecting your code.
Our Java SDK separation between an api
and an impl
module allows your code to remain completely agnostic of the concrete classes and operations we use in order to interact with the backend. The backend does change from time to time and we need to be sure that our SDK can keep properly interacting with it. Therefore, we can (ideally) change our impl
classes and your code will not need to be modified.
Stack
Our SDK is built from the ground up with a modular architecture in mind. The overall architecture is a stack where each module is in charge of a specific responsibility. Said module is available to be used not only by developers but also by other modules that provide higher level functions.
Having such an architecture allows each module to have a small footprint and to re-use much of the already existing code. For instance, take a look at our Spring Boot Starter module. It is only a single Java file! This is the rationale behind how our logic is separated:
- Someone building a standalone Java application can rely on:
Stormpath Java SDK
- If you are building a web application in a non-spring environment:
Stormpath Servlet Plugin
- If you want to build a Spring Web application:
Stormpath Spring WebMVC
- Do you want to have Spring Security in your non-web Spring Boot application?:
Stormpath Spring Boot Starter Security
- You prefer to run Stormpath as a Gateway rather than having it inside your web application?:
Stormpath Zuul Gateway
- And so on…
Network-agnostic
Stormpath is a user management service that is hosted in the cloud. This means that the Java SDK needs to interact with a remote service that is running outside of your domain. Many developers assume that they will need deal with some network-related coding at some point. The good news is that they are wrong.
Our SDK completely abstracts the network nature of Stormpath. You will not need to do anything network-related for the SDK to be fully functional. Our SDK will automatically communicate with our REST API where data will be securely and efficiently transported for you. Remote operations (like login, data updates, etc) will happen behind the scenes without you even noticing they are traveling through the wire. The SDK caches most of the data locally, so operations are efficient and I/O is done in a responsible way.
Integrations
The rest of this post is a deep-dive into working with the Java SDK. We’ve used the SDK to build up the integrations pictured above.
If you are not interested in close-to-the-metal software development using the Java SDK or you’re already using one of the Spring variants or Servlet in your project – good news!
Using one of the many integrations we provide enables you to use Stormpath with little to no additional coding on your part.
For instance, if you drop the stormpath-webmvc-spring-boot-starter
module into your project, your Spring Boot app gets an /oauth/token
endpoint with absolutely no additional coding on your part.
Wanna get down and dirty with the Java SDK? Read on…
Internal Concepts
Data Model
The main objective of the Java SDK is to provide a Java idiomatic development experience to interact with our Rest API-based backend service. This implies that the operations that it provides must be analogous to what can be done via a REST command hitting the backend directly.
In order to accomplish that we modeled the resources provided by the backend. All of these resources are Java interfaces and their hierarchy is as follows:
- A
Resource
represents any entity that actually exists in the backend. This means that is has anhref
(i.e the ID that univocally identifies it for perpetuity)Account
s,Tenant
s,Group
s… etc are allResource
s
- Entities that can hold
Account
s areAccountStore
s.- A
Directory
is a real account holder since accounts exists only inside the directory that holds them. This means that when the directory is deleted the account will also stop existing - A
Group
is a virtual repository where Accounts can be added and removed without really affecting their existence
- A
- An
AccountStoreHolder
denotes a resource capable of referencing account stores.- For example
Application
s andOrganization
s. - You can think of an
Organization
as a ‘virtual’ AccountStore that ‘wraps’ otherAccountStore
s
- For example
Executing Operations in the Backend
The above class diagram provides a simplified view of the data provided by the SDK which mimics what the Rest API provides in the backend. Hopefully, you now have an understanding of how the Resources are represented in Java. There is still one important aspect missing: the operations that they support.
All the operations are provided by Java methods available via each corresponding resource. For example, in order to update the username of an Account
you would do a POST with REST like this (using HTTPie):
http -a APIKEY_ID_HERE:APIKEY_SECRET_HERE \ POST https://api.stormpath.com/v1/accounts/1GFIRBu2pAE3POp0kE3ekE username=fooBar
However with the Java SDK the operation will be as simple as:
account.setUsername(“fooBar”); account.save()
Or, in order to create an Application you would execute the following curl command:
curl -X POST --user APIKEY_ID_HERE:APIKEY_SECRET_HERE \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{ "name" : "FooApp" }' \ https://api.stormpath.com/v1/applications
While with the Java SDK you can simply do:
Application app = client.instantiate(Application.class); app.setName("FooApp”); client.createApplication(app);
These code snippets are useful to exemplify how some of the operations are available via the specific resources:
Resources
’ properties are applied via the corresponding setters- The analogous operation to the POST method is
save()
- An
Application
can be created via theClient
instance which provides acreateApplication
operation (among others) - Tenant credentials are not needed, the SDK takes care of that behind the scenes
Of course there are many more types of operations that the backend provides, like:
- login attempts,
- adding social authentication,
- using multi-factor authentication,
- managing the registration workflow,
- etc.
The intention of this section was to show a simple example of how the REST API operations are mapped in the Java SDK. You can read more about all the available operations in our Java Product Guide.
Volatile State
Our Java SDK works by modifying data that actually resides in our backend. The SDK does not have a state per-se but it does keep data in a cache in order to improve processing speed. Our default cache mechanism keeps data in memory.
This volatile state is not shared by different instances. Each process will have its own independent state, all of them modifying the same data in Stormpath’s backend but that data is not proactively pushed to the SDK in any way. The data is only available locally when retrieved (pulled) by the SDK when needed. These data is later on updated locally only via write operations or cache timeouts. This means that the backend never pushes data to the SDK, the SDK has to pull it instead.
If your application requires different instances of the Java SDK to work concurrently in a distributed environment then you will need to use a distributed cache like Hazelcast. The good news is that we already provide a Hazelcast implementation which is readily available to be used as well.
Developer API
As its name denotes the Java SDK is meant to be used by developers to create their own applications enriched with Stormpath-related functions. They will therefore need to write code in order to programmatically interact with us. Our programming model therefore needs to be simple to use, where simple means: well documented, consistent in experience, and intuitive.
Our Javadocs are your friend and can be found here. Jumping right into the javadocs can be daunting, however. Let’s take a look at some examples that exemplify the consistent experience in the Java SDK.
Patterns
The first thing you need in order to work with Stormpath from the Java SDK is a Client
. The easiest way to get a hold of a Client
is like so:
Client client = Clients.builder().build();
NOTE: Creating a client is something you should only do once in your application. It references an internal CacheManager
and creating multiple copies could create state management issues.
There’s a lot of Java SDK goodness hidden in that one line! You can see we use a builder pattern for creating objects. The builder pattern enables the creation of objects without explicit instantiation (using the new
keyword). This is very important to the design of the SDK as it enables the separation of the api
layer from the impl
layer.
Let’s see how you can get some more out of Client
by providing some configuration.
Configuration
In order to create a Client
object, the Java SDK needs to know the base URL for the Stormpath environment you are working with and it needs to have a set of API keys in order to authenticate against the Stormpath API backend.
The Java SDK uses some sensible defaults to reduce the amount of coding you need to do. It will automatically look for the API keys in: ~/.stormpath/apiKey.properties
. And, it will use the community cloud base URL by default: https://api.stormpath.com/v1
.
The one-liner above would work without alteration under these circumstances. However, the fluent interface and the builder pattern keep the code very readable if you are not using these defaults.
ApiKey apiKey = ApiKeys.builder() .setFileLocation("/path/to/apiKey.properties") .build(); Client client = Clients.builder() .setApiKey(apiKey) .setBaseUrl("https://enterprise.stormpath.io/v1") .build();
In this scenario, your API key file is in an alternate location (/path/to/apiKey.properties
) and you’re using Stormpath’s Enterprise environment (https://enterprise.stormpath.io/v1
).
Notice how even the ApiKeys
class uses the builder pattern – consistency!
Let’s take a look at how you can use a request pattern to interact further with the Java SDK.
OAuth 2.0 Requests
Amongst the most powerful features of Stormpath is our OAuth 2.0 service. The Java SDK provides a consistent interface for working with common OAuth2 workflows. For a more in-depth look at OAuth2, look here.
Building on the previous section, use the Client
to get a hold of an Application
. You can make the application interact with OAuth2 quite easily.
Client client = Clients.builder().build(); Application application = client .getApplications(where(name().eqIgnoreCase("My Application"))) .single();
The above code shows how you can search for an Application
by name using criteria. This code example uses a fluent pattern; one that is used for collections all throughout the Java SDK. The fluent interface allows for method chaining and a method terminator to return a concrete object (an Application
in this case).
One of the common OAuth2 flows is obtaining access and refresh tokens. Another common flow is using a refresh token to obtain a new access token (its only purpose in life).
OAuthGrantRequestAuthentication request = OAuthRequests.OAUTH_PASSWORD_GRANT_REQUEST.builder() .setLogin("me@me.com") .setPassword("super_secret") .build(); OAuthGrantRequestAuthenticationResult result = Authenticators.OAUTH_PASSWORD_GRANT_REQUEST_AUTHENTICATOR .forApplication(application) .authenticate(request); String accessToken = result.getAccessTokenString(); String refreshToken = result.getRefreshTokenString();
There are two distinct parts in the code above: (1) building a request (line 1) and (2) authenticating using the request (line 6).
See the fluent interface and builder patterns at work?
Now, look at how you can refresh the access token:
OAuthGrantRequestAuthentication refreshRequest = OAuthRequests.OAUTH_REFRESH_TOKEN_REQUEST.builder() .setRefreshToken(refreshToken) .build(); result = Authenticators.OAUTH_REFRESH_TOKEN_REQUEST_AUTHENTICATOR .forApplication(application) .authenticate(refreshRequest);
Look familiar? Once again, this builds a request and gets a result.
Pop-quiz: What do all of the above code examples NOT have? Answer: the new
keyword. If you stick to using the api packages and the SDK design patterns, we can improve the implementations without you ever having to update your code.
That’s why we recommend having the api
module as a compile-time dependency and the impl
module as a runtime dependency. Here’s what that looks like in a pom.xml
file:
4.0.0 ...... com.stormpath.sdk stormpath-sdk-api ${stormpath.version} compile com.stormpath.sdk stormpath-sdk-httpclient ${stormpath.version} runtime
You can see by now that fluent interface, builder pattern, request pattern, and search criteria are used all over to provide a consistent and readable developer experience with the Java SDK.
Kudos
Thanks to Stormpath’s Mario Antollini and Micah Silverman for writing most of the this post and the Java SDK itself.
Summary
Stormpath’s Java SDK was built to help developers create their own applications enriched with Stormpath-related functionality. Our programming model uses a style that’s familiar to Java developers. You can easily invoke REST commands to the backend, without worrying about networking or connectivity. Its modular design allows you to pick the component you need and the web integrations don’t require you to write any code. Finally, it’s well documented with tutorials and examples, as well as Javadocs.
Stormpath’s Java SDK is fully open source on GitHub, with an Apache 2.0 license.
The post The Architecture of Stormpath’s Java SDK appeared first on Stormpath User Identity API.