This repository holds proof of concepts related to Go web applications. All concepts applied will be documented here. The applied concepts will be documented on the application itself, using go docs.
The architecture presented here is aimed toward the hexagonal architecture. To achieve it, interfaces are used as much as possible to isolate every external resource from the domain. This leads to a lot of indirection, which is resolved by a dependency injection container. FX was chosen to fulfil this purpose.
As mentioned earlier, FX is used to resolve and inject all dependencies in this application. FX is built with two basic concepts: construction and invocation.
This application delegates the responsibility of defining all constructors and invokables from a package itself. Each package that requires DI has a module, declaring the constructors and invokables. Outer packages will import inner packages and compose their own module with the imported modules. This is propagated up to the main application modules. The main application module is then used to run the application.
Constructors are used to building a given dependency. FX uses registered constructors to resolve the dependencies from other constructors.
Invokables are functions called after all dependencies are resolved. FX calls these with the resulting dependencies. They are used to kickstart the application and perform actions that are typically executed once in the application lifetime. Their use includes, but is not limited to opening database connections, registering HTTP routes.
One of the most common patterns in go is accepting interfaces and return structs. This, along with the client-side interface definition, has the power to make testing extremely simple. The downside is that it creates a lot of indirection.
FX allows us to resolve this indirection by mapping interfaces to implementations. Because this application is composed of modules that compose other modules, resolving this mapping is pretty straightforward and should be done ate the module importing both client and provider.
With this approach, adapters are completely isolated and are consumed - through the ports - by the use cases.
See the whole user module for complete examples.
A quick reference for the ports and adapters present on the application can be found bellow. Detailed documentation is present on their packages.
Protocol buffers are stores in the protos directory and are managed by Buf. See their documentation for details.
The GRPC server module contains only the server initialization and is not related to the application itself. Each port (eg. handlers, etc...) will be defined on the GRPC module of their domain, such as the Order module.
The Kafka provides the Kafka config struct to producers and consumers.
HTTP is handled by Fiber. The main server setup can be found on Fiber's module. Handlers (i.e. adapters) can be found on the HTTP package of each domain, such as the Order handler and HTTP module.
Integration tests are organized either by domain or by the integration they are testing. E.g. producer-consumer test, grpc test.
Configs are managed by Viper. They are loaded by FX at the application's startup. The Viper struct is also registered in the container and can be used as a service if needed.
The application loads and registers configs as any other dependency: in the FX lifecycle. As with other modules, the configs module can be nested and combined to form the main configs module. See configs package for details.