go-clean-arch
go-clean-arch copied to clipboard
Multi-tenancy logic related to repository, usecase or handler layer?
@bxcodec thanks for your amazing work! Go is just a hobby for me and I'm having fun. I'm learning a lot from your project.
I'm trying to understand if multi-tenancy column/table based is something to be "included", if it is "related to" the repository, usecase or handler level.
Example
Adding tenant.go
model like this:
package models
type Tenant struct {
ID int64 `json:"id"`
Name string `json:"name"`
}
to other models like this:
package models
type Author struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"` // <--- here
Tenant *Tenant // <--- here
Name string `json:"name"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
and
package models
import "time"
type Article struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"` // <--- here
Tenant *Tenant // <--- here
Title string `json:"title" validate:"required"`
Content string `json:"content" validate:"required"`
Author Author `json:"author"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
Let's talk for example of the Store()
method:
- from the delivery (
article_handler.go
) level I callArticleUsecase.Store()
- in Article Usecase
Store()
I callarticleRepo.Store()
- in Article Repository
Store()
I save the article object.
Question
Let's say my tenant_id
is a field of a User
struct in context
on every request (authenticated by a third party middleware).
Where do you think I should do something like below? In handler, usecase or repository?
tenantID := GetTenantIDFromUserInContext()
article.TenantID = tenantID
Doubts about fetch queries
Today, before I discover the amazing "clean architecture", I'm using a where
clause in my SQL queries (https://github.com/go-pg/pg/issues/1179), like this:
// Used as: "q.Apply(FilterByTenant(ctx))"
func FilterByTenant(ctx context.Context) func(q *orm.Query) (*orm.Query, error) {
user := ctx.Value(auth.CTXKeyUser).(*models.User)
return func(q *orm.Query) (*orm.Query, error) {
q = q.Where("tenant_id = ?", user.TenantID)
return q, nil
}
}
I think maybe the concept of FilterByTenant
in the usecase layer is an unnecessary repetition and should belong to lower levels like repositories?
But I also think that the main multi-tenancy logic does not change with the change of possible repository types (Postgres, Mysql, Mongo, microservices).
What do you think about it?
Hi @frederikhors sorry for late reply, so busy lately, hope you doing good.
I'll try to answer your question based on what I've done.
Where do you think I should do something like below? In handler, usecase or repository?
tenantID := GetTenantIDFromUserInContext()
article.TenantID = tenantID
I will put it in the usecase. Is context here means like the context.Context
package?
Or something else?
Have you thought about user-repository? And that repo will be injected to the article or whatever who need the details? like a tenant or stuff?
Is
context
here means like thecontext.Context
package?
Yep.
Have you thought about user-repository? And that repo will be injected to the article or whatever who need the details? like a tenant or stuff?
I don't understand the question.
Oh my god, I missed this. So sorry @frederikhors tough time back then.
Let's schedule some time to discuss this properly. I want to learn how's your approach to the solution, it may be useful to add other use cases in this repo?