Post on 29-May-2015
description
Hexagonal Design in Django
Maarten van Schaik
Django
• Rapid application development• ./manage.py startapp polls• Define some models, forms, views and we’re
in business!
Applications live and grow
• More features are added:– API access– Reporting– Command Line Interface– Integration with other applications– Who knows what else…
Connected Design
• Components can access all other components• When you need to access a piece of data or a
piece of logic, just import and use it• Development is fast
Modular design
• Access to other components goes through well-defined interfaces (API’s) using well-defined protocols
• Components have high cohesion• Components are loosely
coupled
Connected vs Modular
Ports and Adapters
Ports and Adapters
• Specific adapter for each use of the application, e.g. web view, command line, message queue, etc.
• Each adapter connects to a port of the application
• Mock adapters and test harnesses facilitate testing
• Testing becomes easier and faster
Typical Django app
• __init__.py• admin.py• forms.py• models.py• tests.py• urls.py• views.py
App
Uh oh…
• Where is my application?
??GU
I
ModelsViews
Forms
Persistence
Refactoring Django apps
• Rules– Core domain model cannot depend on Django– Tell objects, ask values
Implications
• Core model cannot depend on framework– Core model cannot derive from models.Model– Communication with Django goes through
adapters• Tell, don’t ask– Views should render from immutable values– So no vote.save() in views.py!
Example – Poll
def vote(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) try: selected_choice = p.choice_set.get( pk=request.POST[‘choice’]) except (KeyError, Choice.DoesNotExist): return render(request, …, {error: …}) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(…)
Example – Poll
def vote(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) try: selected_choice = p.choice_set.get( pk=request.POST[‘choice’]) except (KeyError, Choice.DoesNotExist): return render(request, …, {error: …}) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(…)
Mutating data!
Example – Poll (2)
def vote(request, poll_id): try: poll_engine.register_vote(poll_id, request.POST[‘choice’]) except Exception as e: return render(request, …, {error: …}) else: return HttpResponseRedirect(…)
Example – Poll (2)
## Poll engine
def register_vote(poll_id, choice_id): p = Poll.objects.get(pk=poll_id) selected_choice = p.choice_set.get(pk=choice_id) selected_choice.votes += 1 selected_choice.save()
Example – Poll (2)
## Poll engine
def register_vote(poll_id, choice_id): p = Poll.objects.get(pk=poll_id) selected_choice = p.choice_set.get(pk=choice_id) selected_choice.votes += 1 selected_choice.save()
Dependency on Django models
Example – Poll (3)
## Poll engine
def register_vote(poll_id, choice_id): if not poll_repository.choice_exists(poll_id, choice_id): raise PollException(…)
poll_repository.increment_vote_count(choice_id)
Example – Poll (3)
## Django model adapter
def choice_exists(poll_id, choice_id): return Choice.objects.filter( poll_id=poll_id, pk=choice_id).exists()
def increment_vote_count(choice_id) Choice.objects.filter(pk=choice_id).update( votes=F(‘votes’)+1)
Conclusions
• Hexagonal design will help keep speed of adding new features constant
• Encourages modularity and encapsulation• Encourages clean and well-organized
applications• Tests become faster when using plain objects
and data• Django models are not that useful without
coupling with them
That’s it
Thanks and references
• Matt Wynne: Hexagonal Rails• Kent Beck: To Design or Not To Design?• Alistair Cockburn: Hexagonal Architecture • Django tutorial