Categories
go

go – sharing private code in module based apps

Image for post

In this article I want to describe the possibilities to share private common code between multiple go services. This private shared code should not be valid to public audience. Public repositories will not fit this requirement – so they are out.

I have found the following possibilities

1. Plain Source Files Via Git Submodules

For this we do not create a module from the shared code. We leave the code as simple source files and push them to a git repository. In our “main” go service apps we can now clone the submodule in the corresponding folder. The code will then be part of the apps module.

I do not like this approach very much, because I like to work with modules so I can simply use my preferred project location and so on and so forth. But this can be done! What brings us to the next solution.

2. Shared Module

This solution I tried first, because it sounds very simple and a good option to start with. But this one has a tricky thing which I do not recognize at the first time I tried. But now the example project structure for it.

project
├── service_1
|   └── go.mod (project.com/service_1)
├── service_2
|   └── go.mod (project.com/service_2)
└── shared
    ├── go.mod (project.com/shared)
    ├── util
    └── my-util.go

I have two service apps which should use the shared module. I created a module for every service app and the shared code. So far so good. But this will not work out of the box. When I tried to use the shared module code inside the service. I get it not imported. I tried to import “company.com/shared/mypackage”. So I need to link the shared module in my service module where I wanted to use the shared code. OK – I thought then I require the shared module in the service go.mod with:

module project.com/service_1
go 1.15

require (
    project.com/shared v0.0.0
)

But this could obviously not work, because at this address there is no repository, but go wanted to downloads the code from it like with every other required repository here. That is the error message which I get then:

cannot find module providing package project.com/shared/mypackage: unrecognized import path "project.com/shared/mypackage": reading https://project.com/shared/mypackage?go-get=1: 404 Not Found

However there is an option when requiring modules to replace the online location with a local location. And then it will work totally fine:

module project.com/service_1
go 1.15

require (
    project.com/shared v0.0.0
)

replace project.com/shared => ./shared

I have to say that I like this approach very much for private shared module code, because I can use modules for the shared code and can combine it with git submodules to get updated between the services. I can use it in one project with different app modules or in different project. And the import path do not have to be a real repository which I think it’s good, because it is then in independent from the location which is not when using private repositories – like in the next approach.

3. Private Repository

The last possibility I want to describe is using a private repository on github or azure devops. I like to use azure devops for my private repos, pipelines and the other cool stuff which azure devops provides. So the example will be based on this.

Creating the shared Module

So when creating the shared module you have to keep in mind that the module naming has to be the location of your origin repository. It should be something like this:

~/shared
$ go mod init dev.azure.com/{company_or_account_name}/{project}/_git/{repository_name_of_my_shared_code}.git
Using the shared module

After you have initialized the module in this way and pushed it with your shared code to origin, it is available for getting it by “go get”. Then you can go to your service module where you want to use the shared module and proceed a “go get”.

But before we need to make sure we have access to it! This can be done over ssh or https. I prefer the https method. (If you want it to use ssl method or need further explanation -> go get in azure repos). I decided to use the http method, so I need then to create a PAT (personal access token) in azure devops. After then I need to add the following line in my git.config.

[url "https://{user}:{pat}@dev.azure.com/{company}/{project}/_git/{repository_name_of_my_shared_code}.git"]
    insteadOf = https://dev.azure.com/{company}/{project}/_git/{repository_name_of_my_shared_code}.git

The user can be anything but not empty! The pat is your generated personal access token and the other information should be clear.

~/service_1
$ go get dev.azure.com/{company_or_account_name}/{project}/_git/{repository_name_of_my_shared_code}.git

You might be wondering which branch will be used by the “go get” process. It is the default branch. But this can be changed by adding the branch name at the end of repository when you call “go get”.

~/service_1
$ go get dev.azure.com/{company_or_account_name}/{project}/_git/{repository_name_of_my_shared_code}.git@{branch_name}
Using with Docker

Using the shared module in docker will produce first an error by default, because docker can not use the added security in the local git.config. Before you run the “go get” command in docker you have to provide it in dockers global git.config file.

RUN git config --global url."https://{user}:{pat}@dev.azure.com/{company}/{project}/_git/{repository_name_of_my_shared_code}.git".insteadOf "https://dev.azure.com"

RUN go get ./...

Conclusion

I do not like the first approach, because with this I am not dealing with modules and this has the known disadvantages. I think this could be more complicated because of the go root paths etc. So I would not recommend it.

The local shared module method without having a real go repository is very nice, because it is possible to do this without any repository and good to start fast without creating a real go repository. In combination with git submodules it is very flexible and modular to use. And you can use a local module name which is independent from location where the code is hosted. does not work for go repositories.

And then the private repositories. This was my preferred solution, because it works a little in the way like “npm” or “nuget” package managment. The shared module has a better version control else with submodules (at least for me). So the only thing I do not like is that the import paths hold information about the location from the code and in my point of view this is not good, but I read some other post from people who like it – so …