Golang and clean architecture

Reshef Sharvit
ITNEXT
Published in
6 min readJun 22, 2021

--

Not only Dame Time but also Go Time

I wanted to publish this article during the early days of the NBA Playoffs, when Damian Lillard was balling. Although it’s a bit too late now, he still deserves a big shout out.

In this post we will review and deploy a golang application that follows the clean architecture principles.
Source code can be found here.

  • Update: 3.7.2021: Removed OpenAPI client generation because it didn’t help much (and I also had to write validations/errors manually), created request-response model instead.

Intro:

Most of us have probably worked on large projects where parts of the code were so difficult to extend, understand and sometimes even maintain.
Sometimes the mess is so serious, that nobody wants to touch that part/component/system and before you know it, you’re stuck with a system that needs some serious refactoring, or even worse, needs to be completely re-written.

The size of the team only matters to some extent, because when the software is poorly architected, sooner or later it becomes difficult to extend the system with new functionality, and as mentioned above, the difficulties can be so serious that even maintenance becomes too taxing.

Fortunately, software that is well architected remains easy to change and understand as the projects grows and keeps the developers focused on supporting the business.

Uncle Bob’s “Clean Architecture” book provides guidelines and in depth explanations and examples of how to make sure your project doesn’t rot over time.
This is an absolutely must read book.

A project that follows the clean architecture principles will be:

1.UI independent: changing the UI should not affect other parts of the projects.
2.Framework independent: it doesn’t matter what library you’re using. frameworks will be used as tools and will not force any business rules.
3. Database independent: the project shouldn’t care or depend on the chosen database. moreover, we would want to choose our database as late as possible in order to maintain flexibility
4. Testable: a system that is testable is a system that you have confidence working with — therefor it’s a system that is easy to extend or maintain.
5. strict on it’s dependency rules: source code dependencies can only point INWARDS, meaning that an inner circle will know nothing about an outer circle.

Our application’s overall architecture:

Technology used:
Golang, Gin (golang)
AWS: Lambda, API Gateway with custom authorizer, DynamoDB.

Directory structure:

.
├── api
│ ├── handler
│ │ ├── handler.go
│ │ ├── helpers.go
│ │ └── model.go
│ └── middleware
│ └── middleware.go
├── cloudformation
│ └── apigateway
│ ├── apig.yaml
│ └── create_apigateway.sh
├── entity
│ ├── entity.go
│ ├── errors.go
│ └── validations.go
├── go.mod
├── go.sum
├── lambdas
│ ├── authorizer
│ │ ├── deploy.sh
│ │ ├── main.go
│ │ └── template.yaml
│ └── users
│ ├── deploy.sh
│ ├── env.json
│ ├── main.go
│ └── template.yaml
├── Makefile
├── openapi.yaml
├── pkg
│ └── jwtparser
│ ├── jwtparser.go
│ └── jwtparser_test.go
├── README.md
├── repository
│ ├── dynamodb
│ │ └── dynmodb.go
│ ├── mockdatabase
│ │ └── mockdatabase.go
│ └── mysql
│ ├── init.sql
│ ├── model.go
│ └── mysql.go
└── usecase
└── users
├── repository.go
├── service.go
├── usecase.go
├── users.go
└── users_test.go
17 directories, 33 files

Clean Architecture:

clean architecture is made of four circles

Clean Architecture with Java 11 | LaptrinhX
clean architecture four circles

Entities:

Entities encapsulate the most general and high-level business rules.
They are the least likely to change when something external changes.

The entities do not depend on any of the other circles.

The entity package also contains validations and custom errors.

Use Case:

The use case layer contains application specific business rules.

The use case layer only depends on the entities. change in the entities will require a change in the use case layer, but changes to other layers won’t.

In our application these are the sign up, sign in and say hello methods.

Interface Adapters:

Interface adapters layer contains a set of adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as the Database or the Web.

In the api directory we have the handler and middleware packages.
the handler handles the http requests, responses and validations:

and the middleware:

Frameworks and Adapters:

This layer is where all the details go: the Web is a detail and so is the database.

In the Frameworks and Adapters layer we have the Database, UI, etc.
This is the most external level, and is bound to change more frequently than other circles. However, changes here should not affect inner circles.

Let’s see how it all comes together:

First, need to set your S3 artifact bucket in the Makefile.
if you don’t have a bucket, then run:

aws s3api create-bucket --bucket <BUCKET_NAME> --region <REGION>

Deploy the users and authorizer lambdas:

make build && make deploy

And then, deploy our api gateway.

reshef ~ $ make deploy-apigWaiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - aws-lambda-go-apigateway
https://acrymgwwwf.execute-api.us-east-1.amazonaws.com/deploy/

The last line in the script’s output contains our api endpoint that we’ll be working with — https://gjfwhy1kzg.execute-api.us-east-1.amazonaws.com/deploy/

API Endpoints

sign up:

reshef ~ $ curl --location --request POST 'https://acrymgwwwf.execute-api.us-east-1.amazonaws.com/deploy/users/signup' --header 'Content-Type: application/json' --data-raw '{"username": "batz", "password": "batz123456", "address": "mapo 4", "first_name": "batz", "last_name": "hatzav"}'
{"message":"user successfully created"}

sign in:

reshef ~ $ curl --location --request POST 'https://acrymgwwwf.execute-api.us-east-1.amazonaws.com/deploy/users/signin' --header 'Content-Type: application/json' --data-raw '{"username": "batz", "password": "batz123456"}'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjQyODE0NTUsImlhdCI6MTYyNDI3Nzg1NSwidXNlcm5hbWUiOiJ5b3plciJ9.qht3rQoKymZeIBDUBhn5My4CPMexR_8ItqbsOMhYaxM"}

/hello endpoint:
With the token received from the previous api call, we send a request to the /hello endpoint

reshef ~ $ curl --location --request GET 'https://acrymgwwwf.execute-api.us-east-1.amazonaws.com/deploy/hello' --header 'Content-Type: application/json' --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MjQyODUyOTIsImlhdCI6MTYyNDI4MTY5MiwidXNlcm5hbWUiOiJiYXR6In0.L19gD2MOPBma5yBd43UIHBj6ThdCYLl9_jn3wRPHBpo'
{"message":"Hello batz"}

Tackling the clean architecture approach

Personally I like the combination of Lambda and DynamoDB. Both are serverless, scale amazingly and save you lots of maintenance and operation time.
DynamoDB can easily (when properly configured) handle (almost) any number of concurrent requests, without the need to use a connection pool, setup an RDS proxy or anything similar.

However, in our imaginary company, after some time, our business started growing at a high pace, and the product manager asks us to add some more features.
After breaking down the requirements, we end up realizing that a NoSQL database is no longer suited for the task, and we decide to switch to an SQL database.

That is totally fine: we may start by doing something in a certain way, and as the business grows, the needs change, and things we were not able to anticipate and had no way of knowing happen.

In our repository directory we have a package named mysql

The only file we need to modify is the main.go file in the users lambda.
that would be the diff:

main.go for dynamodb on the left. for mysql on the right.

Minimal changes.
To deploy our changes, again we run:

make build
make deploy

and we’re good to go.

PS: take into consideration that in real life scenarios we will need to migrate the existing data from our old database to the new one, and for an SQL database, preferably have a database migration tool for managing the database schema. I recommend flyway for this task.

Now think how easy it can be to migrate from AWS lambda to Kubernetes or other platforms, or from Gin to Echo, etc.
This way it’s easy to switch, change and develop both the technology and the business.

Hope you find it useful.

--

--