Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-tenant ServiceStack API, same deployment to respond to requests on different hostnames?

We're creating APIs using ServiceStack that are multi-tenant. We want to do DNS-based load-balancing and routing, rather than stitch things up via a reverse proxy (like nginx or haproxy).

We have Request DTOs that have a Tenant parameter. ServiceStack (and its SwaggerFeature) allow us to define custom routes, and document the DTOs such that we can read values from path, query, headers, or body.

How do we (best) wire things so that DTO properties can read values from a hostname pattern as well? So, make the Route take values from matching out of the hostname as well as the path?

We'd like to have URLs like

  • https://{tenant}.{DNS zone for environment}/{rest of path with tokens}

Also - out DNS zone will vary depending which environment we're in - for non-production we use (say) testing-foobar.com, and production we use real-live.com. Ideally we'd be able to support both with a single route declaration (and we prefer decorating the Request DTO instead of imperative declaration at run-time AppHost.Init).

like image 731
Peter Mounce Avatar asked Aug 13 '13 10:08

Peter Mounce


1 Answers

I solved this just this week, on a existing multi-tenant system which uses .NET security principals to deal with the user permissions and tenants. I used a custom ServiceRunner to select the tenant and set up the security. Your approach to multi-tenant is different, but using a ServiceRunner still seems a valid approach.

You'd end up with something like this:

public class MyServiceRunner<T> : ServiceRunner<T>
{
    public MyServiceRunner(IAppHost appHost, ActionContext actionContext)
        : base(appHost, actionContext)
    {}

    public override void BeforeEachRequest(IRequestContext requestContext, T request)
    {
        // Set backend authentication before the requests are processed.
        if(request instanceof ITenantRequest)
        {
            Uri uri = new Uri(requestContext.AbsoluteUri);
            string tenant = uri.Host; // Or whatever logic you need...
            ((ITenantRequest).Tenant = tenant;
        }
    }
}

public class MyAppHost : AppHostBase
{
    public MyAppHost() : base("My Web Services", typeof(MyService).Assembly) { }

    public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
    {
        return new MyServiceRunner<TRequest>(this, actionContext);
    }

    public override void Configure(Container container)
    {
        ...
    }
}

Perhaps the Requests filtering approach is somehow better, but this does the job for us.

like image 167
AVee Avatar answered Jan 05 '23 02:01

AVee