stygis — Golang Hexagonal Architecture

Shodiq Muhammad
9 min readJan 15, 2020

Go language, one of the programming languages designed to be so easy and customizable has been a source of the problem. No, there is NO bad thing about golang’s design. But, we as the programmer isn’t born to write the nice and readable golang code. Usually, We have the first programming language that Taught us to code like a baby programmer. Also, the example that we usually took on the internet from any programming language and any source.

Those really influenced us to write something that isn’t specifically for the golang programming language that we wrote. There is also a reason for “make it first” thing that makes the code unorganized. Which makes the code even harder to modify when you need something to improve or adding more features to your project. And maybe you could keep up with the Agile Development when just because of the messy code.

Not Stopping there, the unit test is a must for making your code quality better. There, when the code structure and organization come to help your work. I personally have the experience to write a unit test for a function that doesn’t have a layered structure code. I mean, this is a unit test, not an integration test, why should I write the mocks until the end of the flow. Okay, enough for my personal thing. Then I decided to make something that may help someone to build an application code written in Go Language.

I already knew that something famous has existed on the internet about golang structure based on DDD, the Domain-Driven Design. Which called Clean Architecture, which separates all of the functional packages based on its domain. My friend also told me about Package-Oriented Design. But, one really took my interest to learn it, which is Hexagonal Architecture. Why? First, we need to take a look at the chart below:

source: https://docs.google.com/drawings/d/1E_hx5B4czRVFVhGJbrbPDlb_JFxJC8fYB86OMzZuAhg/edit

Hexagonal Architecture, also called Ports and Adapters Architecture is separating the input-output API from the user interface or other 3rd parties, the Business Logics or also called the use-case, and the Infrastructure modules. Separating these 3 entities comes with its specialty. Because your business logic inside the domain layer doesn’t need to worry anything about the technologies you use for your project. So basically, it supports you to make any improvement of performance on the tech or make changes because your product manager wants to make something new on your business flow.

One thing I found interesting is the hexagonal architecture even separating the main app / the binary based on its domain and API. So for example, if something happened that broke your binary API rest for Domain user, it won’t break the binary APIs for Domain comments, or maybe even the API gRPC or NSQ Consumer for Domain user. There is also a case where a domain needs persistence data from another domain. This is weird in my opinion if we talk based on Clean Architecture because the function to get the persistence data is written inside another domain. I mean, you need to call another domain inside a domain. That’s why separating those 3 entities is helping your project development.

Another little problem that depends on each programmer, is naming and simplicity. First of all, I really don’t like writing code too much. I found that writing data structure for every entity and every domain somehow needed. The reason why it’s because it helps you to separate things and quickly changes something on your code. Yes, and No. It makes your code more complicated and takes time to trace. And That Really Needs Consistency to write many structs or data structures.

Naming, on the other hand, depends on each programmer, because it is seriously the hardest part of coding. You can take a look at Video Conference about Go Best Practices by Ashley McNamara on YouTube below.

Basically, naming on golang is about writing short, clear, and explainable name, but also don’t repeat something. So, the simple and readable golang is applied to your project’s code. But I found that I need to write the interface of functions outside of the domain and for each entity, for example, UserStorage or UserRepository. This makes me wonder why it should so, and made me find it by myself, not from the internet.

So, my theory is because that is something owned by that entity. No really, First I thought that it is because you can’t easily find it by just ctrl + left-clicking on that function if you’re using the Visual Studio Code / VS Code. And then I think it is not following the rule itself because it is repeating the name itself repository.UserRepository, even tho that interface doesn’t get exported. But, if you take a look at Hexagonal Architecture, the infrastructures are centralized to the domain.

So, to put things organized, this needs A Rule. Infrastructure interfaces are owned by domain, used only for initialize function. To put it into perspective, It is a public infrastructure that anybody can use (any domain) but owned by private enterprise (domain). And also, it is weird if you use that interface, for another thing. And golang doesn’t allow it if not all registered functions inside that interface are being called inside the initialize function. So, the naming is much better, for example, user.Persistence or user.Repository.

Because everything is centralized to the domain, the use-case layer is much more special. there’s one file (initiator.go) for the code written inside is all about declaration of interfaces and the initialize function of the domain itself. Mocking, in this case, is much easier thanks to go generate. Because the interfaces are inside one file centralized to one package. the mocks generating is a one-line thing, and not writing the mocks generating for each entity. for example, I only write

//go:generate mockgen -destination=../../../mocks/user_mock.go -package=mocks -source=initiator.go

inside the initiator.go file for all interfaces mock and let the go generate do the work.

There are commons packages that it is needed to put on the project’s root directory, but don’t name it commons or utils. Which the platform that is used by the project and mocks. First, mock is Not the main internal package of your project, it is needed to make things easier while writing the unit test. Don’t make things complicated to put mock for each domain. And Platform is all the technologies that are used by your projects, like Postgres, Redis, etc.. Here it only contains the functions that register or opening connections based on its need. so the main apps and your domains have something fixed to be used.

Here I introduce my repository on GitHub about all I described before, named stygis. This project focuses on simplified code, organized structure and better naming for functions and packages name, and No duplicate naming for the package. Also focusing on the ease of writing the unit test. Be aware that this may not be the better structure for your application, but I’m hoping to write into more general and stay simple.

Here is the structure of stygis, I’m going to explain the development flow of this structure:

stygis/├── bin
├── cmd
├── user
├── rest
├── internal
├── constants
├── model
├── query
├── state
├── glue
├── routing
├── handler
├── rest
├── module
├── user
├── initiator.go
....
├── repository
├── storage
├── cache
├── persistence
├── mocks
├── platform
├── postgres
├── redis
├── routers

root directories explanation:

  1. bin/
    This package is where your main application is saved, or technically this is the destination for go build on every main application.
  2. cmd/
    Write any main application here, but you need to be tidy even for the main app. For the first package, name it the same as the domain inside the module or use-case, and then you have to name the next package the API it uses, for example, REST technology, NSQ technology, or gRPC technology.
  3. internal/
    this uses the basic rule the golang has, a package named internal is restricted and only can be used inside the project itself. Because this contains the business logic and the flow of data for this project only. Meaning it shouldn’t be exported outside of the project.
  4. mocks/
    Centralized mock from all package and all interfaces in this project is generated here. I already provided the step by step to mock the interfaces and the easiest way where to write the interfaces.
  5. platform/
    Any technology the project uses is written in this package, but the function written here must be a collection of functions that can be used multiple times inside multiple packages.

The Development Flow:

constants (optional) -> glue (optional) -> handler -> module -> repository and/or storage
  1. contants/model/
    depends on the development you may need to add some structs for data structuring that later can be used to the end of the flow.
  2. constants/query/
    be minded of writing the query, because this is the key to the data logic for persistence. avoid writing queries multiple times when the logic is just the same. also avoid selecting sensitive data inside your query, use the rule for selecting sensitive data. for example, selecting a password while using userID only isn’t something needed, it could be dangerous.
  3. constants/state/
    every domain has its own unique unchanged parameter, write any const value inside this package, and the file’s name must the one which uses it.
  4. glue/
    another name of middleware, this package contains packages of the middleware from the technologies are being used.
  5. glue/routing/
    this is the middleware of the HTTP router for assigning the handler to the HTTP router.
  6. handler/rest/
    The requests from API are being organized, whether conversions the value, checking bad payloads, etc..techology
  7. module/
    The business logic for processing the requests, everything that domain business logic has is centralized here, and write the log inside this package only
  8. repository/
    This is different from the repository package you usually know. This package contains ONLY the data logic from multiple storages. for example, a function contains a way of taking data from persistence if there’s no data from caching, and stores it to cache before returning. So, business logic only contains the logic of your business.
  9. storage/
    the only package where the technology taking data from its source is written here. Be sure the functions are not doing the same thing, and taking or saving the sensitive data within the rule you make.

You can read more about the details on GitHub, I made the description of every package on readme there. Please create an issue on GitHub if you have any concerns about the project or the structure I made. Thank you for reading my first public project and its story.

How TO Mock

Before you start to generate the mocks for any domain. Make sure that all interfaces are in initiator.go under your domain package.

  1. `inside every initiator.go, put this
//go:generate mockgen -destination=../../../mocks/(source)/(file name destination).go -source=initiator.go` under your `package yourdomain

under the first line or package declaration line, for example:

//go:generate mockgen -destination=../../../mocks/user/user_mock.go -source=initiator.go

2. run go generate ./...on your terminal (your directory position is the root of your project) this will runs the command you put inside the initiator.go the -destination=../../../mocks/(source/domain) is to set the generated files to be all under mocks folder in your root project, so all mocks will be centralized under mocks package

Rules and Explanation:

1. the package name (in the file) must not be the same as the source

2. to call the mock, write the package name (in the file). for example: mock_user.NewMockCaching(ctrl)

When you write the import name mock_user on VS CODE it automatically generates the import it needsmock_user “github.com/stygis/mock/user". Why it must be mock_(source/domain). Because this is not a package of something to implement on the app. you cannot call the mock just using user.NewMockCaching(ctrl)or mock.NewMockCaching(ctrl). This is because the unit test should be written manually, and the programmer who’s writing it understands that a unit test is important as the main code.

Also for a technical reason, if we put the generated mock inside one package mocks as I did before. the other domain cannot make the mock because of the same name of interfaces. This has a good side because you don’t get many mock functions if the mocks are centralized inside one package, and the code and structure stay clean and organized.

Fun Fact: stygis is the name of a squadron of the martian army in an anime called Aldnoah.Zero, the docking stations are hexagonal shape and the way of the spaceships connect and detach is something like hexagonal architecture, ports and adapters. and there is a center ship that connects to the other 4 ships, which is centralized to the domain in this project.

wiki: https://aldnoahzero.fandom.com/wiki/Stygis_Fighter

--

--

Shodiq Muhammad

Adventure Enthusiast, Student of Any of Topics, Sci-Fi, and Fantasy Dreamer. Technology, Practice, Thinking, and Theory are the life!