Search code examples
apirestgomulti-tenant

Multi tenant implementations of the same endpoint


I have an API server written in Go that does some work for different tenants. I have many endpoints that should implements different code based on the tenant that call it, so, for example:

s.GET("/api/orders", a.getOrders)

will call the a.getOrders handler that after its work will returns the same JSON structure for all the tenants but the implementation to get the data could be different (sometimes for a tenant I need to call another web service, for another one I need to query different DB tables etc...). I'm thinking to create different packages for every tenant, so I'll have a common (for the common implementations), tenanta (for tenant A specific implementations), tenantb, tenantc and so on... Now, my question is: which is the best way to handle the "redirection"? The first (and probably bad) thing I can think is to put a switch in my a.getOrders handler and parse the tenantID from the session or the url:

switch tenantID {
   case "tenanta":
       tenanta.getOrders()
   case "tenantb":
       tenantb.getOrders()
   case "tenantc":
       tenantc.getOrders()
   default:
       common.getOrders()
}

Obviously it could became long pretty fast (at the moment I'd have to handle 20+ tenants). Is there a better way to handle this situation?

Thank you


Solution

  • You can do a tenant interface something like

    type tenant interface{
        getOrders() Orders
    }
    

    Now you can declare any number of tenants that implement this interface

    package main
    
    import (
        "fmt"
    )
    
    type tenant interface {
        getOrders()
    }
    
    type TenantA struct {
    }
    
    func (t TenantA) getOrders() {
        fmt.Println("Tenant A")
    }
    
    var tenantMap = map[string]tenant{
        "T-A": TenantA{},
    }
    
    func main() {
        fmt.Println("Hello")
        teneantTest := "T-A"
        
        curTeneant, ok := tenantMap[teneantTest]
        if !ok {
            fmt.Println("Not Found")
            return
        }
        
        curTeneant.getOrders()
    }
    

    Now all your tenants follow same interface and this will be compile time testable if all tenants have minimum set of functions defined as well

    This will also lead to cleaner abstraction