Customizable & SaaSy REST APIs
by Juan Gutierrez
A little about myself...
- Software Engineer
- First conference presentation
-
Soon to be husband
- Video game/comic book nut
- Musician
- http://juannyg.com

Topics
- Problem statement
- Context
- Examples
-
Alternative solutions
- Demo
- Pros & Cons
- Effectiveness
- Future features
Stack
- Django
- Django Rest Framework
- Routers (SimpleRouter)
- ViewSets
So what's the problem?
- SaaS company
- Public API/web services
- Client business rules
- How do we ensure:
- Code maintainability
- Clarity of customizations
- Testability
- Uniform/Centralized logging
- Meaningful documentation

Keep things consistent!
OrderGroove

http://ordergroove.com
Customizations
- Tax totals
- Shipping costs
- Pricing
- Coupons
- Bulk Discounts
- Extra Data
- PO#s
- Customer category
-
"Unstructured data"
Possible solutions...
Client code in core...
class OrderView(ViewSetMixin):# core methods/etcdef shipping_total(self):# do core stuffif self.client == 'client1':# do client1 stuffelif self.client == 'client2':# do client2 stuff
/order
try/except client imports
class OrderView(ViewSetMixin):# core methods/etcdef shipping_total(self):# do core stufftry:# import client module# do client stuffexcept:# nevermind# more core stuff
Custom URLs
- urls.py maintenance
- platform fragmentation
- Clunky :-(
Wouldn't it be nice if...
- URLs were consistent
- Methods/Classes was consistent
- Filenames were consistent
-
Filesystem paths were consistent

SaaSy Django Rest Framework
No URL management!
/order
class OrderView(ViewSetMixin): # core methods/etcdef shipping_total(self):# core stuff
/client1/order
from core import OrderView as CoreOrderViewclass OrderView(CoreOrderView):def shipping_total(self):# custom code# super available - if necessary
Methods/Class Consistency
- Core class
class WebService- Client class
from core.service import WebService as CoreWebService
class WebService(CoreWebService)- Add to REST_FRAMEWORK settings
- Add to Client/Merchant model
class Meta:
saas_url_param = 'merchant_slug'
saas_lookup_field = 'slug'
'SAAS': {'MODEL': 'merchant.models.Merchant', 'MODULE': 'customizations' }
Django REST Framework SaaSy
SaaSRouter
- Client identifier - saas_url_parameter
- Key for client vs core routing
/web/service
/client1/web/service
/client2/web/service
SaaS ViewSet
- Leverages client identifier in URL keyword arg
- At request, determine class to use
class ViewSetMixin(object): ... def as_view(cls, actions=None, **initkwargs): ... def get_cls(...): # determine cls return cls def view(request, *args, **kwargs): ... cls = get_cls(...) self = cls(**initkwargs) return self.dispatch( request, *args, **kwargs) return view
class ViewSetMixin(object): ... def as_view(cls, actions=None, **initkwargs): ... def get_cls(...): # determine cls return cls def view(request, *args, **kwargs): ... cls = get_cls(...) self = cls(**initkwargs) return self.dispatch( request, *args, **kwargs) return viewproject
├── customizations
│ └──client
│ └── app
│ └── subpackage
│ └── module.py
└── app
└── subpackage
└── module.py
File & Path Consistency
- One entry point for merchant code
- Package path is identical to core
- Module name is identical
- ViewSet object has module and path information
- These assumptions are the foundation of this solution
class ViewSetMixin(object):...def as_view(cls, actions=None, **initkwargs):...def get_cls(...):- use value of saas_url_parameter, if available, to lookup merchant record via saas_lookup_field - try importing class from merchant path- if found, cls = merchant class- otherwise, use core classreturn clsdef view(request, *args, **kwargs): # initialize ViewSetreturn view
What have we gained?
- Inheritance!
- Permissions
- Auth
- Serialization
- Methods
- Localized
Demo!
drf_saasy_demo
├── customizations
│ └── tiny-client
│ └── product
│ └── views.py
└── product
└── views.pyPros
- Client extensions are easy to identify
- Core dictates
- HTTP method enforcement
- authentication & permissions
-
Client extension only tweaks what it needs to
- (Should lead to) less duplication of code
- Separation of core vs client concerns
Cons
- Django REST Framework dependancy
How has it worked for us?
Highly dependent on the quality of core classes written,
the way in which they separate concerns from
each other and from methods within the classes.
Still trying to figure out...
- Client specific @link/@action
Features to come
-
Routing via auth
- decorators
- View
- Pre-filtered client QuerySets?
Thanks to
- My Parents
- My soon-to-be wife
- My team @ OrderGroove
- Jorge Escobar
- Michael D'Auria
- Lance Lovette
- Django & DRF teams
- and of course YOU!
Customizable and SaaSy REST APIs
By Juan
Customizable and SaaSy REST APIs
A solution to keep your code base clean in a SaaS environment that is predominantly driven by API web services, which has occasion for minor customization of core web services, based on the needs of your clients.
- 286