Dependencies in Golang projects

Today I will talk about dependencies in Golang projects and how to organize packages using DDD pattern and Mono Repo pattern, this subject are one of the most hardest subjects in Golang world.

When you develop a system using DDD you need set some domains in your system related with business logic, we are an e-commerce company, so we have a product domain.

There are more than one products search API, it’s good because we do a lot of A/B tests. Thinking of DDD, we have a search_a domain and search_b domain.

We defined an interface in product domain to any new search package implement, it’s good because we can insert and drop search apis easily, this interface has a function that returns a slice of products. Product package has a function to inject any search engine implementing Querier, this function will help to query through a specific search engine.

company
+
|
+---+ product
| |
| +---+ product.go
|
|
+---+ search_a
| |
| +---+ search_a.go
|
|
+---+ search_b
|
+---+ search_b.go

product.go

package product
type Product struct{
ID int
name string
}
type Querier interface{
Query() []product.Product
}
func Query(client Querier) []Product {
return client.Query()
}

search_a.go

package search_a
type SearchA struct{}
func (s *SearchA) Query() []product.Product{
// code…
return products
}

search_b.go

package search_b
type SearchB struct{}
func (s *SearchB) Query() []product.Product{
// code…
return products
}
Dependencies: search depending on product.

The idea looks fine, but this architeture has a problem, there are dependencies between domains, both searchs depends on product, but if I want use the search packages in another project, will I need get product package and search package together?

Of course not, lets edit the searchs to decoupling,

search_a.go

package search_a
type SearchA struct{}
type Product struct{
ID int
name string
}
func (s *SearchA) Query() []Product{
// code…
return products
}

search_b.go

package search_b
type SearchB struct{}
type Product struct{
ID int
name string
}
func (s *SearchB) Query() []Product{
// code…
return products
}
Dependencies: product struct inside search package

Both of search packages have your own product struct now, and we have another problem: if I have some business logic in product package, I will need to replicate in both searchs.

It’ not good yet, let’s refactor creating one subpackage per search, this subpackage will call search package.

company
+
|
+---+ product
| |
| +---+ product.go
|
|
+---+ search_a
| |
| +---+ search_a.go
| |
| +---+ product
| |
| +---+ product.go
|
|
+---+ search_b
|
+---+ search_b.go
|
+---+ product
|
+---+ product.go

product.go inside search_a

package product
import (
"github.com/lucasvmiguel/medium_deps/product"
"github.com/lucasvmiguel/medium_deps/search_a"
)
type SearchAProd struct{}
func (s SearchAProd) Query() []product.Product {
sa := search_a.SearchA{}
 productsSearchA := sa.Query()
 return transformProducts(productsSearchA)
}
func transformProducts(products []search_a.Product) []product.Product {
 //transform search_a pkg products to product pkg products
return []product.Product{}
}

product.go inside search_b

package product
import (
"github.com/lucasvmiguel/medium_deps/search_b"
 "github.com/lucasvmiguel/medium_deps/product"
)
type SearchBProd struct{}
func (s SearchBProd) Query() []product.Product {
sb := search_b.SearchB{}
 productsSearchB := sb.Query()
 return transformProducts(productsSearchB)
}
func transformProducts(products []search_b.Product) []product.Product {
 //transform search_b pkg products to product pkg products
return []product.Product{}
}
Dependencies: Subpackage product inside both searchs.
Process
package main
import (
"fmt"
"github.com/lucasvmiguel/medium_deps/product"

search_a_prod "github.com/lucasvmiguel/medium_deps/search_a/product"
search_b_prod "github.com/lucasvmiguel/medium_deps/search_b/product"
)
func main() {
searchA := search_a_prod.SearchAProd{}
 productsFromSearchA := product.Query(searchA)
 searchB := search_b_prod.SearchBProd{}
 productsFromSearchB := product.Query(searchB)
 fmt.Println(productsFromSearchA)
fmt.Println(productsFromSearchB)
}

As you can see, to use query through product package, you just need inject a search engine in product.Query function, so product does not depend on a particular search engine(just need a search engine implementing Querier interface) and no search engine depends on product pkg(because they have your own product struct). Now, the domains are decoupled, you can get search_a or search_b isolated in another project without product domain.

for example:
If you execute this command: “github.com/lucasvmiguel/medium_deps/search_a”
You won’t get sub packages of search_a, as you can see it’s totally decoupled!

I won’t explain why decoupling is good, because there are a lot of articles explaining that, if you want know more about this, check the references of this article.

See the code here.

Happy Coding… =)