Maybe it is possible to build projects with the Java language with a simple code organization, throwing some working code parts and having a just-works project at the end of the day. However, the Java culture is famous for more complicated code structures, abstraction of different layers, creating small, independently testable components, and gluing it all together. Maybe it is not the right name for that, but often, this approach reminds people of enterprise programming.
AWS Lambda functions might seem like small, independent functions and they really are. Our colleagues who write Lambda functions with other programming languages, such as Node.JS or Python, tend to put everything into simple functions, often ignoring code reusability and separation of concerns. It is very easy to find Lambda functions consisting of only a single file with all the business logic. However, often our Lambda functions will have complicated business rules and it will be hard to maintain everything in a single file, and if your project consists of more than one Lambda function, you will need a structure to share common logic between functions.
With Java EE or Spring Framework-based projects, we mostly create different modules for different responsibilities of the project, we test them independently from others, and we glue all these components using the Dependency Injection (DI) pattern. In a classical Domain Driven Design application, we have the service layer that operates over the objects, the repository layer that is busy with persisting those objects, and a presentation layer that consumes the data coming from underlying layers. The presentation layer is mostly set up as a starting point of the project, and we create a Dependency Injection configuration there to configure other backend layers. The implementation can be different between different projects, but the main principle stays same: underlying layers are not aware who is consuming them; they are just created by the Dependency Injection framework and injected to objects that need them.
In the previous chapter, we started with building our real application and implemented a basic Lambda function that authorizes users using an access token. For a production code, we need some logic to query tokens against a repository, and we need a repository to persist User and Token objects. In future, maybe AWS will release a new database solution and we might need to change the data storage engine. These services will be also needed by other Lambda functions, say, to register a new user or obtain a new token, so they should be reusable. In the worst case scenario, we might need to give up AWS Lambda and replace it with a good old MVC framework. Why change business logic in such a case?
To solve these problems, in this chapter, we will write our first service, User Service, and configure the Dependency Injection framework to make them available to the AWS Lambda function. With a Dependency Injection framework, we will use Google's Guice, a lightweight DI framework that also supports JSR-330 specification.
In this chapter, we will cover the following topics:
- Creating User Service
- Configuring Guice
- Writing the Lambda Handler class with injected dependency
- Adding logging
- Service dependencies