Moving from Django Apps to Services

Post on 08-May-2015

968 views 2 download

description

Slides from a talk on moving from Django applications to using services which enable better maintainability and scaling.

Transcript of Moving from Django Apps to Services

Django Apps to Services

Craig Kerstiens@craigkerstiens work at @heroku

Project A collection of configuration and apps for a particular Website.

(per Django Project)

Django

ProjectApp

A collection of configuration and apps for a particular Website.

A web application that does something. I.e. Weblog, Poll, Ticket system

(per Django Project)

Django

Monolithic Monster

Monolithic Monster$ ls mysitemodels.pyviews.pyforms.pyadmin.py

Monolithic Monster$ ls mysitemodels.pyviews.pyforms.pyadmin.py

$ wc -l models.py 2557 models.py$ wc -l view.spy 14241 views.py

Monolithic Monster$ ls mysitemodels.pyviews.pyforms.pyadmin.py

$ wc -l models.py 2557 models.py$ wc -l view.spy 14241 views.py

http://www.slideshare.net/jacobian/the-best-and-worst-of-django

Within Django

Project

Within Django

Project

App

App

App

Within Django

Project

App

App

App

Tickets

FAQ

FaqCreator

Within Django

Project

App

App

App

Tickets

FAQ

FaqCreator

Within Djangocd myproject && find .manage.pyrequirements.txtsettings.pyurls.py./faq/admin.py./faq/forms.py./faq/models.py./faq/urls.py./faq/views.py./faqcreator/admin.py./faqcreator/forms.py./faqcreator/models.py./faqcreator/urls.py./faqcreator/views.py./tickets/admin.py./tickets/forms.py./tickets/models.py./tickets/urls.py./tickets/views.py

Reusability

cd myproject && find .Reusability

cd myproject && find .manage.pyrequirements.txtsettings.pyurls.py./faq/admin.py./faq/forms.py./faq/models.py./faq/urls.py./faq/views.py./faqcreator/admin.py./faqcreator/forms.py./faqcreator/models.py./faqcreator/urls.py./faqcreator/views.py./tickets/admin.py./tickets/forms.py./tickets/models.py./tickets/urls.py./tickets/views.py

Reusability

cd myproject && find .manage.pyrequirements.txtsettings.pyurls.py

cat myproject/requirements.txtfaq==0.1faqcreator==0.1 tickets==0.1

cd myproject && find .manage.pyrequirements.txtsettings.pyurls.py./faq/admin.py./faq/forms.py./faq/models.py./faq/urls.py./faq/views.py./faqcreator/admin.py./faqcreator/forms.py./faqcreator/models.py./faqcreator/urls.py./faqcreator/views.py./tickets/admin.py./tickets/forms.py./tickets/models.py./tickets/urls.py./tickets/views.py

-->

Reusability

REUSABILITY means faster features due to DRY (Don’t Repeat Yourself )

REUSABILITY means faster features due to DRY (Don’t Repeat Yourself )

REUSABILITY does not always mean SCALABILITY or MAINTAINABILITY

ProjectApp

A collection of configuration and apps for a particular Website.

A web application that does something. I.e. Weblog, Poll, Ticket system

(per Django Project)

Django

ProjectApp

A collection of configuration and apps for a particular Website.

A web application that does something. I.e. Weblog, Poll, Ticket system

Django

Service Method of communication over the web.Web APIs allow combination of multiple services

Tech imitate TeamsProject

App

App

App

Tickets

FAQ

FaqCreator

Company

Support

Knowledge Base

Support

Knowledge Base

Teams

App

App

Support

Knowledge Base

Teams Grow

App

App

Support

Knowledge Base

Billing

Teams Grow

App

App

Support

Knowledge Base

Billing

Marketing

Teams Grow

App

App

Support

Knowledge Base

Billing

Marketing

Analytics

Mobile

API

Front End

Social

Teams Grow

App

App

App

Support

Knowledge Base

Billing

Apps GrowMarketing

Analytics

Mobile

API

Front End

Social3 Apps

App

App

App

Support

Knowledge Base

Billing

Apps GrowApp

App

App

Marketing

Analytics

Mobile

App

App

App

API

Front End

Social9 Apps

Support

Knowledge Base

Billing

Apps GrowMarketing

Analytics

Mobile

API

Front End

Social

9 Apps

1 Codebase

SOA To the Rescue

SOA To the Rescue

SOA To the Rescue

Photo by Paul Downey (psd on flickr)

Defined contract for communicatingService

Defined contract for communicatingService

$ curl -O $FAQ_API/create/ -X “question=my\ question source=123”

In Python

In Python

data = {‘question’: “my question”,‘source’: 123}requests.POST(os.environ[‘FAQ_API’] + ‘/create/’, data=data)

More than just API

www.google.com

More than just API

www.google.commail.google.com

More than just API

www.google.commail.google.comcalendar.google.com

More than just API

www.google.commail.google.comcalendar.google.com..............google.com

More than just API

Types of Services

site

Types of Services

sitesubdomain

Types of Services

sitesubdomainAPI

Types of Services

sitesubdomainAPI - no auth required

Types of Services

sitesubdomainAPI - no auth required - API auth required

Types of Services

sitesubdomainAPI - no auth required - API auth required - user auth required

Types of Services

App

App

App

Support

Knowledge Base

Billing

Where to StartApp

App

App

Marketing

Analytics

Mobile

App

App

App

API

Front End

Social

App

App

Support

Knowledge Base

Billing

Where to StartAppMarketing

Analytics

Mobile App

API

Front End

Social

1 JavascriptMany pages

LA POSTA

Project

App

App

App

Tickets

FAQ

FaqCreator

Within Djangocd myproject && find .manage.pyrequirements.txtsettings.pyurls.py./faq/admin.py./faq/forms.py./faq/models.py./faq/urls.py./faq/views.py./faqcreator/admin.py./faqcreator/forms.py./faqcreator/models.py./faqcreator/urls.py./faqcreator/views.py./tickets/admin.py./tickets/forms.py./tickets/models.py./tickets/urls.py./tickets/views.py

Apps Depend on Other Apps

Apps Depend on Other Appsfaq creator

Apps Depend on Other Appsfaq creator

faq app

Apps Depend on Other Appsfaq creator

ticket

faq app

Apps Depend on Other Appsfaq creator

ticket

faq app

faq app

Apps Depend on Other Appsfaq creator

faq app version == 0.1

faq app version == 0.2ticket

Apps Depend on Other Appspip install faq==0.1pip install faq==0.2pip freezefaq==0.2

Apps Depend on Other Appsfaq creator

faq app version == 0.1

faq app version == 0.2ticket

Back to APIs

What’s a serviceProvider API_HOST= http://127.0.0.1

What’s a serviceProviderEndpoint

API_HOST= http://127.0.0.1

/v1/create/

What’s a serviceProviderEndpoint

API_HOST= http://127.0.0.1

/v1/create/

Contract {‘question’: ‘foo bar’,‘source’: 123}

App

AppModelsViewsURLs

Service

AppModelsViewsURLs

Provider Endpoint Contract

A SERVICE means REUSABILITY and enables SCALABILITY and MAINTAINABILITY

Where to start

Where to start1. If you’re monolithic, break up the apps first

Where to start1. If you’re monolithic, break up the apps first2. Port something that doesn’t require user

Where to start1. If you’re monolithic, break up the apps first2. Port something that doesn’t require user3. Port where you have many dependencies

Porting an Appmodels.py

class Faq(models.Model): title = models.CharField(max_length=100) source = models.IntegerField(blank=True, null=True) description = models.TextField() solution = models.TextField() def __unicode__(self): return self.title

Porting an App

faqcreator/views.py

from faq.models import faq

i = Faq(title=‘foo’, description=‘bar’, solution=’la posta’)i.save()

Turn it into an API

Turn it into an API1. pip install django-tastypie

Turn it into an API1. pip install django-tastypie2. add tastypie to INSTALLED_APPS

Turn it into an API1. pip install django-tastypie2. add tastypie to INSTALLED_APPS3. define your APIs

Create your APIfaq/api.py

from tastypie.resources import ModelResourcefrom faq.models import Faqfrom django.contrib.auth.models import Userfrom tastypie.authentication import BasicAuthentication, DjangoAuthorizationfrom tastypie.authorization import Authorization

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create(bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIapi.py

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create( bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIapi.py

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create( bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIapi.py

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create( bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIapi.py

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create( bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIapi.py

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create( bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIapi.py

class FaqResource(ModelResource): class Meta: queryset = Faq.objects.all() resource_name = 'faq' list_allowed_methods = ['get', 'post'] authentication = BasicAuthentication() authorization = Authorization()

def obj_create(self, bundle, request=None, **kwargs): return super(FaqResource, self).obj_create( bundle, request, user=request.user)

def apply_authorization_limits(self, request, object_list): return object_list.filter(user=request.user)

Create your APIurls.py

from faq.api import FaqResourcefrom tastypie.api import Api

v1_api = Api(api_name='v1')v1_api.register(FaqResource())

urlpatterns = patterns('', url(r'^api/', include(v1_api.urls)),)

curl -H "Content-Type: application/json" --user admin:testing -X POST --data '{"title": "New FAQ", "source": "1", "description": "foo", "solution": "bar"}' http://127.0.0.1:8000/api/v1/faq/

Use your API

Use it in Python

Use it in Pythondata = { 'title': 'New FAQ', 'source': '1', 'description': 'foo', 'solution': 'bar'}

r = requests.post(os.environ[‘FAQ_API’] + "api/v1/faq/", data=data, auth=('admin', 'testing'))

Version Bump

Version Bumpdata = { 'title': 'New FAQ', 'source': '1', 'description': 'foo', 'solution': 'bar', 'related': ['123', '456']}

r = requests.post(os.environ[‘FAQ_API’] + "api/v2/faq/", data=data, auth=('admin', 'testing'))

Version Bump

data = { 'title': 'New FAQ', 'source': '1', 'description': 'foo', 'solution': 'bar', 'related': ['123', '456']}

r = requests.post(os.environ[‘FAQ_API’] + "api/v2/faq/", data=data, auth=('admin', 'testing'))

Version Bump

In our Applicationfaq_creator/views.py

ticket/views.py

r = requests.post(os.environ[‘FAQ_API’] + "api/v2/faq/", data=data, auth=('admin', 'testing'))

In our Application

r = requests.post(os.environ[‘FAQ_API’] + "api/v1/faq/", data=data, auth=('admin', 'testing'))

faq_creator/views.py

ticket/views.py

cd myproject && find .manage.pyrequirements.txtsettings.pyurls.py

cat myproject/requirements.txtfaq==0.1faqcreator==0.1 tickets==0.1

cd myproject && find .manage.pyrequirements.txtsettings.pyurls.py./faq/admin.py./faq/forms.py./faq/models.py./faq/urls.py./faq/views.py./faqcreator/admin.py./faqcreator/forms.py./faqcreator/models.py./faqcreator/urls.py./faqcreator/views.py./tickets/admin.py./tickets/forms.py./tickets/models.py./tickets/urls.py./tickets/views.py

-->

Reusability

cd myproject && find .

manage.pyrequirements.txtsettings.pyurls.py

cat myproject/requirements.txt

faq==0.1faqcreator==0.1 tickets==0.1

-->

Maintainability/Scalability/Agilityls projectsfaq-servicemyproject

cd faq-service && find .manage.pyrequirements.txtsettings.pyurls.py

cd myproject && find .manage.pyrequirements.txtsettings.pyurls.py

cat myproject/requirements.txtfaqcreator==0.1 tickets==0.1

Maintainability/Scalability/Agility

Maintainability/Scalability/AgilityFaq Project

- Its own Django project- 1 Django app- Own database- Own deployment

Maintainability/Scalability/AgilityFaq Project

- Its own Django project- 1 Django app- Own database- Own deployment

My Project- Contained Django Project- 2 Django apps- Own database- Own deployment

Take aways

Take aways1. Start non-critical (Cheat)2. Create services where theres app reuse3. Create services where theres pain4. Start small

Others

Others

1. Expect APIs to fail2. Flask is great for APIs3. Make it easy to setup/test