"Clean" django architecture - what do you think

312 views
Skip to first unread message

alonn

unread,
Aug 18, 2017, 4:13:00 PM8/18/17
to PyWeb-IL
Most of the time django architecture is around "fat views" vs "fat models"  None play too well with "ports and adapters" architecture (or "onion" or "Uncle bob clean architecture" etc)  I always find it hard to separate Framework code  from entities  and to separate persistence from business logic  (Both used for with models in django) 

Bumped into those two articles in the recent python-weekly :


Which go a long (and hard) way (especially the first) to do the clean separation of the onion architecture and moving business logic to a framework free code (Which can also be immutable).
What do you think about those suggestions? how do you solve this kind of problems and design your django apps?

My current take is Moving all business processes and logic to services, resolvers, calculators etc, But they are called through the model as a facade and sometime (depends on how complicated the separation is) except a model/s as parameter and not regular variables. I also try to wrap results in namedTuples/classes and pass them around and not primitive results

What is your take?

Shai Berger

unread,
Aug 19, 2017, 10:56:39 AM8/19/17
to pywe...@googlegroups.com
Hi,

TL;DR: Separating non-trivial logic from both models and views is a good idea.
Trying to separate it from the framework isn't.

On Friday 18 August 2017 23:12:59 alonn wrote:
>
> Bumped into those two articles in the recent python-weekly :
>
> https://engineering.21buttons.com/clean-architecture-in-django-d326a4ab86a9

This one made me cringe. class GetProductInteractorFactory? Seriously? Haven't
they forgotten "Manager" in that name? And the way they defined the private
vars and public getters in the very first class they present... I think even
modern Java is better than that.

But it goes deeper than the naming and coding style: All of the talk of
Dependency Injection, wrapping everything with redundant levels of indirection
(ViewWrapper)... If you think all of that is appropriate, I would suggest that
Django is not the right framework for you.

A specific point I found worthy of critique is the attempt to make the logic
"framework independent". I think there's about as much sense in that as in an
attempt to make it language independent (hint: the way to tool independence in
the large, goes through microservices).

> http://mitchel.me/2017/django-service-objects/
>

That one seems much more reasonable. I don't agree with all of their choices,
but the general idea of putting non-trivial business logic in a place that is
separate from all of the "built in" candidates -- models, views. forms and
serializers -- that I fully agree with. And that is what I tend to do.

> What do you think about those suggestions? how do you solve this kind of
> problems and design your django apps?

So, usually, simple methods which mostly help bridge the gap between the way I
think of the objects in the app and the way they can be represented in the
database, go in the models (the Django ORM takes care of some of this gap, but
often not all of it). More complex methods can go there too, if they only
touch one model -- "complex" and "simple" here are judgment calls, anyway.

But, as I said above, non-trivial logic, especially when it needs to handle
more than one model, belongs in separate modules, typically called "logic".
Also typically, these modules contain independent functions, not classes --
these calculations should normally not have "state" of their own.

>
> My current take is Moving all business processes and logic to services,
> resolvers, calculators etc, But they are called through the model as a
> facade and sometime (depends on how complicated the separation is) except a
> model/s as parameter and not regular variables. I also try to wrap results
> in namedTuples/classes and pass them around and not primitive results
>

I see the logic level as standing "above" the models level -- that is, logic
uses models, not the other way around. It is important to keep the layers
separate, or else trouble will come one way or another -- forms/serializers
never call or import views, models never import forms or serializers or logic.

Generally speaking, logic and forms/serializers shouldn't be calling each
other -- both should be called from views. But I'm not sure how religious I am
on that.

Whether results should be wrapped is, again, a judgment call (how complex are
the things you return?). This is a real question, not a rhetorical one: What
do you gain by avoiding primitive return values?

HTH, and sorry for the length,

Shai.

alonn

unread,
Aug 19, 2017, 6:47:09 PM8/19/17
to PyWeb-IL
Thanks Shai for your reply. Since we seem to agree on most, I'll focus on three points
1. Where is the "logic" layer. I think separation is the main virtue and calling can be from either view (Where applicable) and then "logic" is probably using models or accepting model result as parameter 
Like this code (From some project of mine)
def summary_view(request, entity_id):
    some_entity = get_object_or_404(SomeEntity, pk=entity_id)
    status = EntityStatusCalculator(obj=some_entity).calculate() # Logic layer gets model as parameter - datasource, non mutating 
    serializer = EntityStatusSerializer(instance=status)

    return JsonResponse({'data': serializer.data})

Or with model as a facade. I do accept the possibility that this not being uni directional  can be a source of trouble in the future and perhaps I'll change that. but again, separation is in my view the important thing here.

About complete abstraction of the framework., this is obviously BS , since you are using the framework for some reason you'd like to benefit from. BUT I do think that some separation of Data Access  should be enforced , and far too many Django projects
We can see model.objects.do_some_aggregate_something.all() sprayed all over the place since models. querysets and models instances are being passed around.  Returning my own Value Objects instead of a queryset helps reduce that.

About custom return objects. Besides reducing the potential of spraying the code with queryset code, I would also rather return my CustomClassWithMeaningfullName (or named tuple) to the view from the service instead of an anonymous dict or array. because of code readability, when I return to this code after a year I can easily understand what is returned without trying to trace back my old logic. 
This also allows me to enforce some checks on the data returned (named tuple would scream if not getting all expected fields) and add some helper methods if needed
Reply all
Reply to author
Forward
0 new messages