building a multi-tenanted saas with node.js

Post on 19-Jan-2017

54 Views

Category:

Technology

6 Downloads

Preview:

Click to see full reader

TRANSCRIPT

111 Steps to Building a Multi-tenanted SaaS in Node.js

Eoin Shanaghy

Edappy

eoinseoinsha

Edappy started with a single-tenanted internal system, delivering University-level courses

Node.js - Seneca - Express - MongoDB

Microservices

Dockerised EC2

The job: Make the platform a multi-tenanted SaaS

The Beginning

“This should be easy”a.edappy.com b.edappy.com

c.edappy.com d.edappy.com

We started to think about Tenant Provisioning.

“How often?”

“How big?”

“How does it relate to the business model?”

“How will we…monitor it, maintain it or deploy it?”

We will have many small tenants. Some will be just evaluating, at least at first. They will be spread internationally and many will use a free tier.

A few will be large, enterprise tenants. They will take longer to publish their content but will have many thousands of students and many, large courses.

Don’t just think production.How do I run a multi-tenanted SaaS in development?

What will my tests look like?

How many processes, containers and VMs will I need to run?

First, share.Duplicating anything increases overhead.

Share resources where possible.

Isolate only when required.

No new hardware.Before adding any containers or instances, maximise every bit of existing infrastructure.

Faster start.

Lower maintenance.

More cost effective.

Actually, one new container

IsolationThere are three ways to isolate tenants’ data.

Document/table per tenant

Collection/Table per tenant

Database per tenant

HTTP - Express - Seneca - Actor - MongoDB

http://senecajs.org/tutorials/understanding-data-entities.html

$> mongo test --eval “shellPrint(db.person.find())"{ "_id" : ObjectId("56563ed8f25f27f90cedeafe"), "name" : "Fred", "age" : 17 }

$> mongo test2 --eval “shellPrint(db.person.find())"{ "_id" : ObjectId("56563ed8f25f27f90cedeaff"), "name" : "Elizabeth", "age" : 27 }

Seneca already gives you:

routing

pattern-based segregation/partitioning

The explicit version:

Transparent, in-band context

Within any actor, I want to know which tenant domain initiated the request.

https://github.com/senecajs/seneca/issues/182

So we wrote…

https://github.com/LSEducation/seneca-context

seneca-context allows you to set or get any data relating to a single request at any point in the action chain, even across transport boundaries

Express

seneca-context

seneca-web

createContext Tenant Registry

action

action

action

transport boundary

POST /api/user/authHost: d.edappy.com

D

D

D

D

“d”?seneca-web plugin

DNSRoute53

Wildcard subdomains first:

*.example.com

*._staging.example.com

*._local.example.com - 127.0.0.1

Programatically registered thereafter

Tenant StoresThere are hundreds of actors

Each actor has 0..* store operations

How do you avoid explicitly setting the zone for every operation?

Write another Seneca pluginUse seneca.wrap

Intercept all entity actions

Look up the zone/context if not already set

Provision the DB, if not already done

Configure the store plugin

Set the zone and call seneca.prior

Now…

Was…

Other parts of the story

HurdlesDon’t create or read any tenant-specific data on init

Tenant ProvisioningRegister a tenant record

Create external resources (bucket)

Configure database

Approximately $0 cost

Integration TestingAt least one instance of each service

At least two tenants

Clean database per suite

Docker Compose

Each test begins outside the app boundary (SuperTest)

Seneca test actions to perform setup/teardown

OversightWe use ELK for aggregated logs across containers and services

Log and index tenant ID

APIs

Actions

Debugging

ScalingUp to a point, adding instances, containers is okay

Large, high value tenants warrant dedicated resources

Limit tenant-specific resources per app instance (DB connections, etc.)

Explicit/Manual, Mesos, Kubernetes

top related