Hexagonal Design in Django

3,235 views

Published on

Flash talk given on 7 aug 2013 for the Amsterdam Python Meetup Group

Published in: Technology, Business

Hexagonal Design in Django

  1. 1. Hexagonal Design in Django Maarten van Schaik
  2. 2. Django • Rapid application development • ./manage.py startapp polls • Define some models, forms, views and we’re in business!
  3. 3. Applications live and grow • More features are added: – API access – Reporting – Command Line Interface – Integration with other applications – Who knows what else…
  4. 4. 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
  5. 5. 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
  6. 6. Connected vs Modular
  7. 7. Ports and Adapters
  8. 8. 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
  9. 9. Typical Django app • __init__.py • admin.py • forms.py • models.py • tests.py • urls.py • views.py
  10. 10. App Uh oh… • Where is my application? ?? Models Views Forms
  11. 11. Refactoring Django apps • Rules – Core domain model cannot depend on Django – Tell objects, ask values
  12. 12. 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!
  13. 13. 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(…)
  14. 14. 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!
  15. 15. 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(…)
  16. 16. 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()
  17. 17. 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
  18. 18. 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)
  19. 19. 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)
  20. 20. 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
  21. 21. That’s it
  22. 22. Thanks and references • Matt Wynne: Hexagonal Rails • Kent Beck: To Design or Not To Design? • Alistair Cockburn: Hexagonal Architecture • Django tutorial

×