Pragmantic SaaS Architecture

Pragmantic SaaS Architecture

A presentation I gave at wearedevelopers about pragmatic SaaS architectures.

181de1fb11dffe39774f3e2e23cda3b6?s=128

Armin Ronacher

May 11, 2017
Tweet

Transcript

  1. Pragmatic SaaS Architecture Armin @mitsuhiko Ronacher

  2. )J *N"SNJO BOE*EP0QFO4PVSDF  MPUTPG1ZUIPOBOE4BB4

  3. lucumr.pocoo.org github.com/mitsuhiko twitter.com/mitsuhiko read discover follow

  4. None
  5. 800°C 36° 2' 0.4662" N 118° 15' 38.7792" W 795°C

    789°C 797°C 793°C 805°C 782°C
  6. I love SaaS

  7. SaaS

  8. Multi Tenant

  9. Managed Cloud? single tenant

  10. But also … On Premises?

  11. Diving In

  12. patterns are universal (examples are Python)

  13. Building Blocks

  14. boring is better than fancy

  15. Postgres & Redis & AMQP (for example)

  16. Foundation

  17. Security First

  18. If you only take one thing away from this talk

  19. context awareness

  20. Tenant Isolation from framework import get_request def get_tenant_from_request(): request =

    get_request() auth = validate_auth(request.headers.get('Authorization')) return Tenant.query.get(auth.tenant_id) def get_current_tenant(): rv = tls.current_tenant if rv is None: rv = get_tenant_from_request() tls.current_tenant = rv return rv
  21. Automatic Tenant Scoping def batch_update_projects(ids, changes): projects = Project.query.filter( Project.id.in_(ids)

    & Project.status != ProjectStatus.INVISIBLE ) for project in projects: update_project(project, changes) DANGER!
  22. Automatic Tenant Scoping class Project(db.Model): id = db.Column(db.Integer, primary_key=True) status

    = db.Column(db.Integer) tenant_id = db.Column(db.Integer, db.ForeignKey('tenants.id')) tenant = db.relationship(Tenant) @classproperty def query(cls): return db.Query(self).filter( Project.tenant == get_current_tenant() )
  23. User Access Scope Restrictions

  24. User Scope & Request Scope def get_current_scopes(): current_user = get_current_user()

    if current_user is None: all_scopes = set(['anonymous']) else: all_scopes = current_user.get_roles() return all_scopes & scopes_from_request_authorization()
  25. Audit Logs

  26. Log Security Related Actions def log(action, message=None): data = {

    'action': action, 'timestamp': datetime.utcnow() } if message is not None: data['message'] = message if request: data['ip'] = get_request().remote_addr user = get_current_user() if user is not None: data['user'] = User db.session.add(LogMessage(**data))
  27. i18n & l10n

  28. Language from User or Request def get_current_language(): user = get_current_user()

    if user is not None: return user.language request = get_current_request() if request and request.accept_languages: return request.accept_languages[0] return 'en_US'
  29. Monolith → SOA

  30. SOA is complex

  31. Start Simple Evolve

  32. Design as you go

  33. Developer Happiness

  34. custom linters on commit mitsuhiko at herzog in ~/Development/sentry on

    git:master+? workon sentry $ git ci -am 'Performance improvements to the data scrubber.' src/sentry/utils/data_scrubber.py:147:1: F401 'unused' imported but unused
  35. master is stable

  36. institutionalize bidirectional compatibility

  37. fast iteration trumps scalability

  38. Quick Release Cycles

  39. make dev and prod look alike

  40. short and small pull requests

  41. feature flag new functionality

  42. QA &