Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. Aplicações Multi 'Tenant' em Django @xima

  2. @xima

  3. vinta.com.br/playbook

  4. FLOSS Django React boilerplate https://github.com/vintasoftware/django-react-boilerplate Django Role Permissions https://github.com/vintasoftware/django-role-permissions Tapioca

    https://github.com/vintasoftware/tapioca-wrapper
  5. O que é multi tenacidade?

  6. "Inquilino" "Locatário"

  7. None
  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
  9. Single Tenant 1 client = 1 server + 1 db

  10. Multi Tenant n clients = 1 server + ? db

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

    a manutenção da infraestrutura; • Simplificar a manutenção da base de código;
  12. Contexto

  13. Corporate Fidget Spinner Tracking™

  14. None
  15. None
  16. None
  17. "Como você vai proteger os nossos dados?"

  18. Single Shared Schema [ou como os gigantes fazem]

  19. None
  20. "Talk is cheap..."

  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
  22. Querying avg_duration = ( Spin.objects .filter(user_spinner__user__customer=request.customer) .aggregate(avg=Avg('duration')))['avg']

  23. None
  24. Simpler querying avg_duration = ( Spin.objects .filter(customer=request.customer) .aggregate(avg=Avg('duration')))['avg']

  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
  26. Drawbacks • Difícil de garantir isolamento; • Pode adicionar complexidade

    ao código; • Problemas de compatibilidade com bibliotecas de terceiros;
  27. Multiple databases

  28. None
  29. Routing DATABASES = { 'default': { 'ENGINE': ..., 'NAME': ...,

    }, 'ibm': { 'ENGINE': ..., 'NAME': ..., } }
  30. The `.using()` approach spinners = ( Spinner.objects .using(request.customer.name) .annotate( avg_duration=Avg('owned_spinners__spins__duration'))

    .order_by('-avg_duration'))
  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
  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']
  33. Querying spinners = ( Spinner.objects .using(request.customer.name) .annotate( avg_duration=Avg('owned_spinners__spins__duration')) .order_by('-avg_duration'))

  34. Multitenacidade de banco vs. Multitenacidade de Aplicação

  35. Single Database Multiple Schemas

  36. None
  37. O que são 'schemas'? SELECT id, name FROM user WHERE

    user.name LIKE 'F%';
  38. O que são 'schemas'? CREATE SCHEMA ibm; SELECT id, name

    FROM ibm.user WHERE ibm.user.name LIKE 'F%';
  39. O `search_path` SET search_path TO ibm; SELECT id, name FROM

    user WHERE user.name LIKE 'F%';
  40. Django-tenant-schemas

  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) # ...
  42. Routing - settings MIDDLEWARE_CLASSES = [ 'tenant_schemas.middleware.TenantMiddleware', # … ]

    DATABASES = { 'default': { 'ENGINE': 'tenant_schemas.postgresql_backend', 'NAME': 'mydb', } }
  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() # ...
  44. Linha de comando ./manage.py tenant_command shell ./manage.py createsuperuser ./manage.py migrate_schemas

  45. Querying spinners = ( Spinner.objects .annotate( avg_duration=Avg('owned_spinners__spins__duration')) .order_by('-avg_duration'))

  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
  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
  48. Pontos Positivos • Abstração das queries; • Criação automática dos

    'schemas'; • Facilita migrações; • Infraestrutura simplificada;
  49. Pontos Negativos • Escalabilidade(?) • Tests um pouco mais lentos;

    • Queries transversais complicadas;
  50. multitenancidade não é discreta, é um expectro contínuo

  51. github.com/filipeximenes/multitenancy blog post -> http://bit.ly/django-tenants

  52. Obrigado! Newsletter: vinta.com.br/blog/ twitter.com/@xima github.com/filipeximenes ximenes@vinta.com.br

  53. Sites framework https://docs.djangoproject.com/en/1.11/ref/contrib/sites/

  54. Feature Toggles • Customizações por tenant; • Pausar bugs; •

    Integração contínua; • Testes A/B;