Spring Boot vs Booster: Implementing a Bank Account Part 1
You’ve probably heard about Domain-Driven Design, the benefits of complementing it with the Command-Query Responsibility Segregation pattern, and that by combining them with an Event-Sourced backend you will raise your developer superpowers to the moon (if you haven’t, check out this link)
I’m here to tell you that all of that is true. But implementing a backend designed in this way isn’t easy. To succeed, you need a reliable team with strong development skills... or do you?
For the last two years, we’ve been working on a new framework called Booster that is specifically designed to build this kind of application, significantly reducing the time-to-market of event-driven applications by taking advantage of modern technologies like Serverless computing and GraphQL.
Although Booster is new in the CQRS-DDD scene, it packs years of experience from our team working with big retail companies and dealing with their challenges (microservices interdependencies,Black Friday peaks, complex model relationships, etc...). Booster builds an entirely new developer (and business) experience that greatly simplifies the process, but the first obvious question you may have is...
Why should I use Booster instead of <my well-established framework>?
That's a great question, and we will try to answer it with some demos and examples. In this piece, we will create a project to examine the strongest points of Booster and see how they compare to building the same application using Spring Boot and other known technologies such as GraphQL, RabbitMQ and MongoDB.
The use case, architectural goals, and tech stacks
To keep the scope small enough but still interesting, let's create a bank account application with these requirements:
- Bank tellers can create customer bank accounts
- Customers and bank tellers can deposit money in any bank account
- Customers can only withdraw money from their bank accounts
- Customers can only check the balance of their bank accounts
- Bank tellers can check the balance of any account
- The account balance is the result of adding and subtracting all the deposits and withdrawals from account creation.
That's enough. With this information, we can design our CQRS-DDD event-sourced application with 2 roles (bank teller and customer), 3 commands (create an account, deposit, and withdraw), and 1 entity (the account itself) so, let's get started.
In case you are not familiar with CQRS and Event Sourcing, here is a diagram with the high-level architectural approach:
Creating the project
- Nodejs v12+
- An AWS account
- Java SDK 1.8+
Creating the project with Booster
The Booster official documentation says the best way to get started is by installing the CLI and generating a new project. So after installation, this is what we have to do:
$ boost new:project booster-bank-account
Creating the project with SpringBoot
As recommended by the Spring Boot documentation, the best way to start a new project is using start.spring.io to configure the project. Let’s use Gradle as the dependency manager, Java as the language, Spring Boot version 2.3.X, and these dependencies:
- Spring Web
- Cloud Stream
- Spring Data MongoDB
- Spring Boot DevTools
- Spring Security
- Oauth2 Resource Server
You can get the same project configuration by clicking here
The code
A blog post is not the best place to review code so here are the repositories for the Booster and for the Spring Boot applications. Take the time you need to review it in detail, below we will make it easy for you and highlight the most important things.
The Project Structure
The commands, events, entities, and read models directories are close to being the same. However, take a look at that infrastructure package in the SpringBoot project. I tried to keep the code simple, trust me, it’s not over-engineered, and has the code required according to the official Spring Boot docs :)
Booster project directory
Spring Boot project directory
If you look carefully at the infrastructure classes, e.g. the DepositHandler, BankAccountRepository or the GraphQL Mutation and Query, you will see that without introducing generics or reflection, the Spring Boot project will grow faster than the Booster project because adding new commands or entities require to implement new handlers and repositories, and add new resolvers to Query and Mutation.
Configuration
The config package is present in both projects with the difference that Booster auto-generated the config file and I had not touched it. On the other hand, and after a lot of study, I had to configure Spring Security to work with JWT tokens without an authorization server. It was challenging because, despite my experience with Spring Boot, I had not configured Spring Security from scratch before. There are 3 security frameworks in Spring: Spring Security, Spring Cloud Security, and Spring Security OAuth. Getting the differences between them, deciding which one, and how to configure it, deserve their own post.
Infrastructure
The infrastructure package in the Spring project contains 18 files that are not necessary with Booster. The package is divided into 4 sub-packages, one per technology plus one for the framework configuration.
The graphql package in the Spring project contains the resolver functions for the mutations and queries. I picked this GraphQL Java Quick Start framework because it has a good integration with Spring Boot (that was a research and decision). In addition to the query and mutation resolvers, it requires you to define a schema file, which in this case, is in the resources directory. Nothing of this is needed in a Booster application because Booster generates the schema and the resolvers from the commands’ and read-models’ code.
Schema file, not needed with Booster
The message package contains the code needed to enqueue and listen to commands and events into RabbitMQ exchanges and queues using Spring Cloud Stream (another decision needed to be made). One reason to use queues is to absorb load peaks and process commands at the server’s pace. Booster achieves the same by using lambda functions for commands and a live stream of changes in the Events database to trigger the event handlers and the reducers, which are also lambdas.
By using RabbitMQ, we have to configure the Spring Cloud Stream framework to properly route the messages to exchanges, and to connect the listeners to the right queues. Booster abstracts you from the messaging server in a different way than Spring Cloud Stream does, it means that you don’t notice if you are working with queues or not. Actually, when the Booster core team changed from AWS Kinesis to the DynamoDB Event Stream, nobody noticed the difference because the streams are created implicitly and you don’t have to deal with any low-level service-specific APIs.
Finally, the mongo package is maybe the simpler one, it contains the interfaces to the Events, Entities, and Users databases (as collections). Booster manages everything of this automatically thanks to the AWS provider. It stores the events in DynamoDB tables, and as I said before, it uses the Dynamo’s change stream feature to wake up the event handlers, reduce the entities and project the read models. I know Mongo has a change stream feature similar to Dynamo, but at this point, I did not want to invest more time in researching technologies.
Authorization and Authentication
Securing resources to allow only authorized users to access them is achieved, by Booster and Spring Boot, using annotations.
Booster authorization with decorators
The main difference is that with Booster you don’t have to know the annotations beforehand because it generates the code already annotated. Additionally, annotations use clever names so you can understand the authorization model just by looking at the code. Take a look at those snippets, in the first one we can infer that only bank tellers are authorized to run the CreateBankAccount command. Also, we can see that BankTeller comes from the Roles module, so we quickly conclude that the Booster’s authorization model is based on roles.
On the other hand, what is @PreAuthorize? Where do you define the users’ authorities? What is a SCOPE? Where do I declare them?. The code is not self-documented and much knowledge is required in order to come up with the solution in the second image. Spring Boot is a great framework and it solves really complex problems by providing great flexibility, but great flexibility implies more work for the developer, and some of the capabilities are hard to understand and master.
However, this is only related to the users’ authorities. We haven’t talked about users’ registration, sign in, and sign out. Booster integrates with AWS Cognito to manage production-ready users’ roles, sessions, passwords, and JWT tokens out of the box with no config beyond defining the role classes.
If we take a look at the Spring Boot documentation for OAuth2 authentication, it assumes that the authorization and resource servers are separate instances and that you will use an OAuth2 provider such as GitHub, Facebook, Google or Okta. This introduces a problem because with the exception of Okta, you can not control the tokens issued by those providers to include custom claims, so you would need some token exchanging logic to embed the users’ authorities in internal tokens, or you would create your own OAuth2 authentication server. The bad part is that the Spring Boot OAuth2 authorization server is still experimental, so that will need another research to conclude what to do.
Conclusion
Booster has some clear advantages such as:
- It reduces the number of decisions you need to make and the knowledge required to develop production-ready CQRS-ES applications because it is an opinionated framework that abstracts you from the implementation details.
- You save a lot of time and energy from your team because Booster provides an integrated experience where they don’t have to research the best technology for a given problem, they don’t have to learn how to use it, and neither have to worry about how to configure it.
- With Booster you write less code and get more features. As all the infrastructure is automatically inferred and provisioned for you, it is not needed to write configurations for them. Less written code means fewer bugs and more time for delivering value.
- Booster projects are easier to maintain because you only deal with business logic code. There are no complex APIs to maintain, and the architecture is decoupled by nature when requirements change, it’s easier to change the system behavior without affecting other parts.
Spring Boot is a great framework that helps you to solve almost every problem imaginable, I have worked with it several times in the past, and I love how it eases building microservices architectures. But Spring Boot's incredible flexibility is not free, developers still need to deeply understand the Spring Boot features, navigate through a myriad of available tools and modules, understand the annotations, the dependencies, design the overall architecture, and spend a lot of time searching for documentation in StackOverflow, blogs, and the official references. SpringBoot has a steep learning curve, and you and your team need an adequate level of knowledge to deal with the tech stack.
Booster focuses on doing one thing and doing it simple and right: building event-driven serverless backend services with a GraphQL API. The developers find common problems already solved, with most good practices already implemented and embedded in the framework structure, so it’s easier to learn, and they can focus on the business logic to add value and iterate faster.
Booster is an open-source project under heavy development, we would love to get your feedback, read your comments and why not, see you contributing to the project :)
We are writing more articles about Booster vs other popular frameworks like Ruby on Rails