Git Product home page Git Product logo

poc-multi-tenancy-separate-schemas's Introduction

POC: Multi-tenancy Separate Schemas

It demonstrates how to implement a multi-tenant REST API that persists data in different schemas.

The goal is to develop a REST API capable of persist and retrieve a product catalog. Each product must have a name and a unique ID generated automatically by a database sequence that is going to be assigned to the product when the client requests a new product to be created.

The application should be able to switch among different schemas from a single data source based on the client sending the request to create or find products based on an HTTP header, therefore not mixing products from different clients by using single database instance but different schemas. An error should be returned to clients requesting data without informing a known tenant.

All tenant configuration should be defined outside the application source code using property files, and we must be able to add or remove tenants without changing the source code.

The Web service is implemented using Spring MVC, data is persisted on Postgres databases managed by Flyway using Spring Data JPA. The source code is managed by JUnit with databases provisioned in containers managed by TestContainers and no manual configuration is required to run automated tests.

Other approaches to multi-tenancy are Separate Databases and Shared Schemas.

How to run

Description Command
Run tests ./gradlew test
Run application ./gradlew bootRun

Preview

Overview of this multi-tenant approach:

flowchart TB
api[Product API]
database[(DataSource)]

tenant_a((Tenant A))
tenant_b((Tenant B))
tenant_n((Tenant N))

tenant_a -- GET /products\nX-Tenant-Id: tenant-a --> api
tenant_b -- GET /products\nX-Tenant-Id: tenant-b --> api
tenant_n -. GET /products\nX-Tenant-Id: tenant-n -.-> api

api -- tenant_a.PRODUCT --> database
api -- tenant_b.PRODUCT --> database
api -. tenant_n.PRODUCT -.-> database
Loading

Database structure for the DataSource:

erDiagram
    tenant_a__PRODUCT {
        BIGINT PRODUCT_ID PK
        VARCHAR PRODUCT_NAME
    }
    tenant_b__PRODUCT {
        BIGINT PRODUCT_ID PK
        VARCHAR PRODUCT_NAME
    }
    tenant_n__PRODUCT {
        BIGINT PRODUCT_ID PK
        VARCHAR PRODUCT_NAME
    }
Loading

Symbol __ means ., so public__PRODUCT means public.PRODUCT.

Logging statements from application startup during automated tests:

2022-07-30T11:28:19.147-03:00  INFO 35159 --- [    Test worker] com.example.ApplicationTest              : Starting ApplicationTest using Java 18.0.2 on pc with PID 35159
2022-07-30T11:28:19.147-03:00 DEBUG 35159 --- [    Test worker] com.example.ApplicationTest              : Running with Spring Boot v3.0.0-M4, Spring v6.0.0-M5
2022-07-30T11:28:19.147-03:00  INFO 35159 --- [    Test worker] com.example.ApplicationTest              : No active profile set, falling back to 1 default profile: "default"
2022-07-30T11:28:20.524-03:00 DEBUG 35159 --- [    Test worker] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=public)
2022-07-30T11:28:20.544-03:00 DEBUG 35159 --- [    Test worker] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=public)
2022-07-30T11:28:20.568-03:00 DEBUG 35159 --- [    Test worker] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=public)
2022-07-30T11:28:20.828-03:00  INFO 35159 --- [    Test worker] c.e.internal.TenantsDatabaseInitializer  : Migrating tenant schema (schema=company_x)
2022-07-30T11:28:20.904-03:00  INFO 35159 --- [    Test worker] c.e.internal.TenantsDatabaseInitializer  : Tenant schema migrated successfully (schema=company_x, success=true)
2022-07-30T11:28:20.905-03:00  INFO 35159 --- [    Test worker] c.e.internal.TenantsDatabaseInitializer  : Migrating tenant schema (schema=company_y)
2022-07-30T11:28:20.922-03:00  INFO 35159 --- [    Test worker] c.e.internal.TenantsDatabaseInitializer  : Tenant schema migrated successfully (schema=company_y, success=true)
2022-07-30T11:28:20.923-03:00  INFO 35159 --- [    Test worker] com.example.ApplicationTest              : Started ApplicationTest in 1.915 seconds (process running for 4.305)
2022-07-30T11:28:21.274-03:00 DEBUG 35159 --- [o-auto-1-exec-1] com.example.internal.TenantInterceptor   : Handling request for tenant company_x
2022-07-30T11:28:21.275-03:00 DEBUG 35159 --- [o-auto-1-exec-1] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=company_x)
2022-07-30T11:28:21.316-03:00  INFO 35159 --- [o-auto-1-exec-1] c.example.product.api.ProductController  : Creating new product (request=ProductRequest(name=A4 Paper))
2022-07-30T11:28:21.329-03:00 DEBUG 35159 --- [o-auto-1-exec-1] tsConfiguration$TenantConnectionProvider : Getting connection for a tenant (tenantIdentifier=company_x)
2022-07-30T11:28:21.362-03:00  INFO 35159 --- [o-auto-1-exec-1] c.example.product.api.ProductController  : New product created (product=Product(id=1, name=A4 Paper))
2022-07-30T11:28:21.372-03:00 DEBUG 35159 --- [o-auto-1-exec-1] com.example.internal.TenantInterceptor   : Removed tenant assigned previously before sending response to client
2022-07-30T11:28:21.372-03:00 DEBUG 35159 --- [o-auto-1-exec-1] tsConfiguration$TenantConnectionProvider : Releasing connection for a tenant (tenantIdentifier=company_x)
2022-07-30T11:28:21.395-03:00 DEBUG 35159 --- [o-auto-1-exec-2] com.example.internal.TenantInterceptor   : Handling request for tenant company_x
2022-07-30T11:28:21.396-03:00 DEBUG 35159 --- [o-auto-1-exec-2] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=company_x)
2022-07-30T11:28:21.397-03:00  INFO 35159 --- [o-auto-1-exec-2] c.example.product.api.ProductController  : Creating new product (request=ProductRequest(name=Pencil 1B))
2022-07-30T11:28:21.397-03:00 DEBUG 35159 --- [o-auto-1-exec-2] tsConfiguration$TenantConnectionProvider : Getting connection for a tenant (tenantIdentifier=company_x)
2022-07-30T11:28:21.399-03:00  INFO 35159 --- [o-auto-1-exec-2] c.example.product.api.ProductController  : New product created (product=Product(id=2, name=Pencil 1B))
2022-07-30T11:28:21.400-03:00 DEBUG 35159 --- [o-auto-1-exec-2] com.example.internal.TenantInterceptor   : Removed tenant assigned previously before sending response to client
2022-07-30T11:28:21.400-03:00 DEBUG 35159 --- [o-auto-1-exec-2] tsConfiguration$TenantConnectionProvider : Releasing connection for a tenant (tenantIdentifier=company_x)
2022-07-30T11:28:21.414-03:00 DEBUG 35159 --- [o-auto-1-exec-3] com.example.internal.TenantInterceptor   : Handling request for tenant company_y
2022-07-30T11:28:21.415-03:00 DEBUG 35159 --- [o-auto-1-exec-3] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=company_y)
2022-07-30T11:28:21.416-03:00  INFO 35159 --- [o-auto-1-exec-3] c.example.product.api.ProductController  : Creating new product (request=ProductRequest(name=Eraser))
2022-07-30T11:28:21.416-03:00 DEBUG 35159 --- [o-auto-1-exec-3] tsConfiguration$TenantConnectionProvider : Getting connection for a tenant (tenantIdentifier=company_y)
2022-07-30T11:28:21.418-03:00  INFO 35159 --- [o-auto-1-exec-3] c.example.product.api.ProductController  : New product created (product=Product(id=1, name=Eraser))
2022-07-30T11:28:21.418-03:00 DEBUG 35159 --- [o-auto-1-exec-3] com.example.internal.TenantInterceptor   : Removed tenant assigned previously before sending response to client
2022-07-30T11:28:21.419-03:00 DEBUG 35159 --- [o-auto-1-exec-3] tsConfiguration$TenantConnectionProvider : Releasing connection for a tenant (tenantIdentifier=company_y)
2022-07-30T11:28:21.425-03:00 DEBUG 35159 --- [o-auto-1-exec-4] com.example.internal.TenantInterceptor   : Handling request for tenant company_x
2022-07-30T11:28:21.425-03:00 DEBUG 35159 --- [o-auto-1-exec-4] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=company_x)
2022-07-30T11:28:21.425-03:00  INFO 35159 --- [o-auto-1-exec-4] c.example.product.api.ProductController  : Finding all existing products
2022-07-30T11:28:21.426-03:00 DEBUG 35159 --- [o-auto-1-exec-4] tsConfiguration$TenantConnectionProvider : Getting connection for a tenant (tenantIdentifier=company_x)
2022-07-30T11:28:21.496-03:00  INFO 35159 --- [o-auto-1-exec-4] c.example.product.api.ProductController  : Returning all products found (products=ProductsResponse(products=[ProductResponse(id=1, name=A4 Paper), ProductResponse(id=2, name=Pencil 1B)]))
2022-07-30T11:28:21.503-03:00 DEBUG 35159 --- [o-auto-1-exec-4] com.example.internal.TenantInterceptor   : Removed tenant assigned previously before sending response to client
2022-07-30T11:28:21.503-03:00 DEBUG 35159 --- [o-auto-1-exec-4] tsConfiguration$TenantConnectionProvider : Releasing connection for a tenant (tenantIdentifier=company_x)
2022-07-30T11:28:21.533-03:00 DEBUG 35159 --- [o-auto-1-exec-5] com.example.internal.TenantInterceptor   : Handling request for tenant company_y
2022-07-30T11:28:21.533-03:00 DEBUG 35159 --- [o-auto-1-exec-5] enantsConfiguration$TenantSchemaSelector : Resolving tenant identifier (tenant=company_y)
2022-07-30T11:28:21.533-03:00  INFO 35159 --- [o-auto-1-exec-5] c.example.product.api.ProductController  : Finding all existing products
2022-07-30T11:28:21.533-03:00 DEBUG 35159 --- [o-auto-1-exec-5] tsConfiguration$TenantConnectionProvider : Getting connection for a tenant (tenantIdentifier=company_y)
2022-07-30T11:28:21.536-03:00  INFO 35159 --- [o-auto-1-exec-5] c.example.product.api.ProductController  : Returning all products found (products=ProductsResponse(products=[ProductResponse(id=1, name=Eraser)]))
2022-07-30T11:28:21.537-03:00 DEBUG 35159 --- [o-auto-1-exec-5] com.example.internal.TenantInterceptor   : Removed tenant assigned previously before sending response to client
2022-07-30T11:28:21.537-03:00 DEBUG 35159 --- [o-auto-1-exec-5] tsConfiguration$TenantConnectionProvider : Releasing connection for a tenant (tenantIdentifier=company_y)

poc-multi-tenancy-separate-schemas's People

Contributors

lucasvsme avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

Forkers

donhuvy

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.