Aplicações multi-tenacidade com Django

Aplicações multi-tenacidade com Django

Multi-tenacidade (multitenancy) é característica de um sistema atender vários clientes a partir de uma única instância de software. Esta é uma característica bastante fácil de se verificar em sistemas SaaS mas que pode ser encontrada em diversos níveis e formas em arquiteturas de software.
O caso de uso mais comum de uso de muni-tenacidade é para isolamento de dados entre clientes. Uma forma de fazer isso é através do uso de múltiplos bancos de dados. Outra forma bastante comum é utilizar-se de apenas um banco de dados e modelar relacionamentos nas tabelas de forma que seja possível realizar consultas isoladas. A terceira forma mais comum é, novamente, ter apenas um banco de dados mas desta vez ter 'schemas' separados para cada cliente.
Nesta palestra falarei sobre cada um destas abordagens. Para cada uma delas você entenderá como funciona a arquitetura, como desenvolve-la utilizando Django, verá exemplos de como realizar consultas e conhecerá as ferramentas que podem ajudar no processo de desenvolvimento. Ao fim você será capaz de escolher a abordagem que melhor se adequa a sua próxima aplicação.

Ce373a3291defccc69a1392feb587f17?s=128

Filipe Ximenes

October 08, 2017
Tweet

Transcript

  1. 2.
  2. 7.
  3. 8.

    "... se refere a arquitetura de software onde uma única

    instância de software 'roda' em um servidor e serve múltiplos 'inquilinos'." - [tradução livre] Wikipedia
  4. 11.

    O que queremos? • Reduzir custo de infraestrutura; • Simplificar

    a manutenção da infraestrutura; • Simplificar a manutenção da base de código;
  5. 12.
  6. 14.
  7. 15.
  8. 16.
  9. 19.
  10. 21.

    Routing - ibm.spinnertracking.com def tenant_middleware(get_response): def middleware(request): host = request.get_host().split(':')[0]

    subdomain = host.split('.')[0] try: customer = Customer.objects.get(name=subdomain) except Customer.DoesNotExist: customer = None request.customer = customer response = get_response(request) return response return middleware
  11. 23.
  12. 25.

    Case study: Salesforce • Proporção 1:5000; • Checagem de sanidade;

    • Checagem de software; • Transparência para os desenvolvedores; • https://www.youtube.com/watch?v=jeysYua6ENs
  13. 26.

    Drawbacks • Difícil de garantir isolamento; • Pode adicionar complexidade

    ao código; • Problemas de compatibilidade com bibliotecas de terceiros;
  14. 28.
  15. 29.

    Routing DATABASES = { 'default': { 'ENGINE': ..., 'NAME': ...,

    }, 'ibm': { 'ENGINE': ..., 'NAME': ..., } }
  16. 31.

    The threadlocal middleware approach def multidb_middleware(get_response): def middleware(request): subdomain =

    get_subdomain(request) customer = get_customer(subdomain) request.customer = customer @thread_local(using_db=customer.name) def execute_request(request): return get_response(request) response = execute_request(request) return response return middleware
  17. 32.

    The router class TenantRouter(object): def db_for_read(self, model, **hints): return get_thread_local('using_db',

    'default') def db_for_write(self, model, **hints): return get_thread_local('using_db', 'default') # … # settings.py DATABASE_ROUTERS = ['multitenancy.routers.TenantRouter']
  18. 36.
  19. 38.

    O que são 'schemas'? CREATE SCHEMA ibm; SELECT id, name

    FROM ibm.user WHERE ibm.user.name LIKE 'F%';
  20. 41.

    Routing - middleware # ... connection.set_schema_to_public() hostname = self.hostname_from_request(request) TenantModel

    = get_tenant_model() try: tenant = self.get_tenant(TenantModel, hostname, request) assert isinstance(tenant, TenantModel) except TenantModel.DoesNotExist: # ... request.tenant = tenant connection.set_tenant(request.tenant) # ...
  21. 42.

    Routing - settings MIDDLEWARE_CLASSES = [ 'tenant_schemas.middleware.TenantMiddleware', # … ]

    DATABASES = { 'default': { 'ENGINE': 'tenant_schemas.postgresql_backend', 'NAME': 'mydb', } }
  22. 43.

    Routing - db backend # ... try: cursor_for_search_path.execute( 'SET search_path

    = {0}'.format(','.join(search_paths))) except (django.db.utils.DatabaseError, psycopg2.InternalError): self.search_path_set = False else: self.search_path_set = True if name: cursor_for_search_path.close() # ...
  23. 46.

    SELECT id, duration FROM ibm.spinner_spin WHERE duration > 120 UNION

    SELECT id, duration FROM vinta.spinner_spin WHERE duration > 120; Querying across schemas
  24. 47.

    SELECT uuid, duration FROM ibm.spinner_spin WHERE duration > 120 UNION

    SELECT uuid, duration FROM vinta.spinner_spin WHERE duration > 120; Querying across schemas
  25. 48.

    Pontos Positivos • Abstração das queries; • Criação automática dos

    'schemas'; • Facilita migrações; • Infraestrutura simplificada;
  26. 54.