
Code structure
With any new application, there are many ways to organize code on disk. The following structure has worked very well for my colleagues and me across multiple projects. I encourage readers to use this as a starting point and adapt as needed. If using Node.js or another supported language for your FaaS provider, this may look slightly different. Throughout this chapter, we will fill in our example coffee cupping API and will add more files as we build the application:
├── Makefile
├── README.md
├── envs/
│ ├── dev
│ ├── qa
│ └── production
├── requirements.txt
├── serverless/
│ ├── cupping/
│ ├── handler.py
│ ├── lib/
│ └── serverless.yml
└── tests/
├── conftest.py
├── factories.py
├── helpers.py
├── test_session_model.py
└── test_session_persistence.py
A Makefile may be something you skip. I use Docker as a host for application development since it's a reasonably easy way to manage environment variables during deployment and testing. A simple Makefile can make tedious tasks much less verbose by hiding the complexity behind a make target.
The envs directory holds environment variable files, which are simple key=value pairs. Each environment has a corresponding file of the same name. Looking at preceding the example, it should be clear where the configuration resides for each environment and what you'd need to do when creating a new environment.
We place all the code into the serverless directory, including application code we write, as well as its dependencies. Our application code is namespaced into the cupping directory. We will install third-party libraries into lib. Of course, as you write your application, your application code will be namespaced to something that is appropriate for your project. I recommend using a meaningful name rather than something generic such as code or app, just to aid new developers who come after you in navigating the source tree and for general clarity and explicitness.
Alongside the application code live two files—serverless.yml, which defines and controls the Serverless Framework, and handler.py, which is the main entry point for any API calls. In the preceding diagram, we discussed how logical groupings of API endpoints would be handled by a common function within a given file, handler.py, which will be the entry point for these API calls and delegate the hard to work to other functions. In some ways, handler.py has a straightforward job, which will become apparent.
As responsible developers, we will make sure our code is well tested. With pytest as our testing framework of choice in this project, all unit test files live in a single test folder with some additional helpers and configuration utilities. In the preceding example, there are only two test files; more will be added to the final project. Your exact testing strategy isn't as important as the simple fact of having well-written tests. Serverless projects are incredibly fast to deploy, and there may be an inclination to forego tests. Why write unit tests when you can just deploy it for real and test it manually? One cannot overstate the benefit of having robust unit tests. Regardless of your tooling or language, all serverless projects of any decent size or complexity will benefit from tests, which you may run locally or on a continuous integration platform or system. Tests give us the confidence to deploy quickly and often and also set us up for continuous delivery moving forward, allowing a CD system to deploy our code automatically only after our tests have passed.