Skip to content
This repository has been archived by the owner on May 26, 2023. It is now read-only.

Multi-tenancy logic related to repository, usecase or handler layer? #8

Open
frederikhors opened this issue Feb 14, 2020 · 1 comment

Comments

@frederikhors
Copy link

frederikhors commented Feb 14, 2020

@eminetto 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, service or handler level.

Example

Adding tenant.go model like this:

package entity

type Tenant struct {
	ID   int64  `json:"id"`
	Name string `json:"name"`
}

to other models like this:

package entity

import "time"

type Bookmark struct {
	ID          ID        `json:"id"`
	TenantID    int64     `json:"tenant_id"` // <--- here
	Tenant      *Tenant   // <--- here
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Link        string    `json:"link"`
	Tags        []string  `json:"tags"`
	Favorite    bool      `json:"favorite"`
	CreatedAt   time.Time `json:"created_at"`
}

Let's talk for example of the Store() method:

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, service 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 (go-pg/pg#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 service 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?

@eminetto
Copy link
Owner

As far as I understood, your service need the tenant_id to work, no matter how you generate it. Here, as you are receiving this data from an HTTP request, your HTTP handler should get this from the context and send to the service. 
In the Store function, you can create a test to validate the input, and if the tenant_id is missing return an error. If you implement another ways to receive the tenant_id, for instance, as a parameter in a CLI application, or from an environment variable, you won't need to alter the service and repository layers.
This is the way I probably would implement a solution to this case :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants