An awesome toolkit to support go developers (me) when writing microservices which leverage go-kit. Godin takes care of generating as much code as possible while being opinionated. Generally speaking, Godin will provide templates within your project, specifically for your project. It will then always use these templates to generate code, giving the developer a lot of freedom.
TODO
Initializes a godin project in the current directory. It requires a few options to be set:
--namespace, -n
The project's namespace (use the kubernetes namespace for convenience), lowercase--service, -s
The service name which this project implements, lowercase--module, -m
The module which of the project. Godin will automatically initialize the module for you.--protobuf-module, -p
The module in which the generated protobuf stubs for this service are located.
Example:
godin init --namespace godin --service ticket --module github.com/go-godin/godin/examples/ticket --protobuf-module github.com/go-godin/ticket-service/api
This will leave you with an empty project. You can then proceed and add modules to generate code for you.
Install a module into the current project. A list of available modules is below. When installing a module, two things happen:
- The modules configuration is saved into the
godin.yaml
, which indicates that the module is enabled - The templates of the module are copied into the project (default folder is
templates
). This allows the developer to be able to modify them to fit to fit the use-case you're solving.
Note: A module can only be added once!
Example:
godin add transport.grpc.server
This command is probably the most frequently used. It generates all module's templates based on the configuration.
In order to have access to the protobuf types, you need to pass the source proto-file which defines the API of the
service you are implementing (--protobuf, -p
option).
Godin will render all templates in the project's templates folder into their respective targets (see module info). If a template is missing, a warning is logged. The generate command does not restore any missing files, it will only operate on what you have in the project's templates.
The protobuf path can also be set via the PROTOBUF_FILE
environment variable. That way you could
use a make-target to easily call the generate command like make generate
.
Example:
godin generate -p /some/where/over/the/rainbow.proto
Updates the project templates with the templates of the currently installed godin version. It will only update templates of enabled modules. It will not overwrite existing templates as godin cannot figure out if you've made changes to it.
In order to update all templates, regardless whether they already exist, you have two options:
- Simply remove the templates folder and let godin recreate it
- Use the
--force, -f
option (recommended) which will print a warning but overwrite existing templates
TODO: partially update the templates by module
After a while of writing microservices the boilerplate code to write go-kit services gets annoying. Godin is my attempt of solving that problem leveraging go-kit and trying to obey the clean architecture principles.
Yes, I get that, but why yet another generator?
Well, first of all - it's fun. Secondly I really wasn't content with existing (and my previous) solutions. The reason being that - sooner or later - you have to force a developer down a certain path. There is nothing wrong with having a standard way of doing things. But when writing a code-generator you need to think about a lot of edge cases in order to make it usable.
All previous attempts were abandoned because I was hitting such an edge case and was forced to either ditch the code generator by editing generated code or modify it first before continuing to solve my actual problem.
This led me to the following premises under which this version of godin was developed.
- Shared models are always specified using Protobuf
- The service API is always specified using gRPC services
- Every gRPC service represents it's own subdomain (DDD)
- Godin will not restrict the developer with it's generated files
Have you ever written a code generator before? I did, quite a few recently actually (and you should too). And I've always found it very tempting to just ship one large binary which simply included all available templates (like I did in a previous godin version).
If you start going down that path, you must be confident, that your templates are covering all cases. Because as soon as a developer hits an uncovered edge-case, he is forced to stop using your tool and first make the necessary changes to the tool. Although that might work, I don't really see why one would try to provide centralized templates only.
Don't force the developer to use your templates
So, what if you instead provided all the necessary templates inside the project and let the developer modify them to his needs.
That's way more convenient. Because most of the time, you don't even need to adjust templates (well, depends on your templates :)).
Even better, IF a developer hits an edge-case, he can just keep editing his current project and adjust it to his needs.
godin generate
will continue working => less frustration.
I personally think that this is a good path to go. You can ship your services, based on protobuf, including the templates which lead to most of the code. Everything is under version control, great!
But what if upstream changes?
Consider this: You have custom template modifications on a microservice project. Then you upgrade (git pull) godin (or w/e tool) to the
newest version. Everything that needs to be done at this point is to compare the local (project) templates with the
upstream templates. Essentially, this is a git diff
taking place. If a local modification is found, simply force the developer
to properly merge the files.
That way each project of yours can still have it's own template modifications while keeping godin up to date.
Go only?
Heck, no! This might be completely written in Go, but by no means can godin be used with Go applications only.
As long as the premises apply, you can use any programming language. All you need to do is write templates for it
and maybe introduce some modification into godin.
Modules are logically separated pieces which provide templates and additional functionality. A module is defined by a single interface:
type Module interface {
ConfigProvider
// Configure is used to initialize a module based on some ResolvableConfig which can be
// unmarshalled into the module's own configuration struct.
Configure(source ResolvableConfig) error
// Templates returns all template object which the module uses.
Templates() []Template
// OutputPaths returns a list of paths which must exist in order for the module to correctly generate the
// templates.
OutputPaths() []string
// Install hook is called when 'godin add' is executed for that module. The hook enables the module to interfere
// and prepare the module (e.g. prompt for values).
// The installation is considered a success if error == nil.
Install() error
// Generate is executed when 'godin generate' is called
Generate(projectContext interface{}, protobufContext interface{}, templateRootPath, outputRootPath string) error
}
Modules allow for a separation of concerns within godin. They also make it easier to extend godin without having a profound understanding of it's inner workings.
The module system - although working - is not yet done and might change often.
Source | Target | Note | Overwrite |
---|---|---|---|
transport/grpc/server.go.tmpl |
internal/transport/grpc/server.go |
Generates the gRPC server transport layer | YES |
Source | Target | Note | Overwrite |
---|---|---|---|
transport/grpc/client.go.tmpl |
pkg/grpc/client.go |
Generates the gRPC client transport layer for downstream usage | YES |
Source | Target | Note | Overwrite |
---|---|---|---|
service/interface.go.tmpl |
internal/<serviceName>/service.go |
The main service interface | YES |
service/models.go.tmpl |
internal/<serviceName>/models.go |
Generates the protobuf messages (except request and response) as well as enums as a base for your domain-models. | NO |
Source | Target | Note | Overwrite |
---|---|---|---|
endpoint/endpoints.go.tmpl |
internal/endpoint/endpoints.go |
The endpoint set | YES |
endpoint/request_response.go.tmpl |
internal/endpoint/request_response.go |
The internally used request and response structs | YES |