[HN Gopher] Django for Startup Founders: A better software archi...
       ___________________________________________________________________
        
       Django for Startup Founders: A better software architecture for
       SaaS startups
        
       Author : Alex3917
       Score  : 256 points
       Date   : 2021-06-23 14:38 UTC (8 hours ago)
        
 (HTM) web link (alexkrupp.typepad.com)
 (TXT) w3m dump (alexkrupp.typepad.com)
        
       | travisjungroth wrote:
       | I'm a big Django fan and a former (unsuccessful) startup founder.
       | I only took a quick look at this but wanted to leave a comment
       | for anyone else sifting through new. My _initial_ impression:
       | 
       | 1. There's a bunch of things a I disagree with in here.
       | 
       | 2. This is very well written.
       | 
       | 3. It's well organized.
       | 
       | 4. It's both concrete _and_ high-level.
       | 
       | 5. There's a ton of info here.
       | 
       | I'm so burnt out on shallow content, it's really refreshing to
       | see something well argued, whether I agree with every conclusion
       | or not.
        
         | mamcx wrote:
         | Agreed. The most important take, IMHO, is that you have
         | structured/disciplined way to do things, and do it everywhere.
         | 
         | It could lead to a lot of code (the part about all that
         | validations upfront), but having a distinct and regular style
         | surely is a benefit for a large-sh team, that look like is the
         | focus.
         | 
         | For a _smaller_ one, some of this stuff can be different, but
         | the point is well argued.
        
         | Alex3917 wrote:
         | > There's a bunch of things a I disagree with in here.
         | 
         | This is largely how I feel about things like Two Scoops of
         | Django or Effective Python -- I disagree with a lot of the
         | specific advice, but I still learned a ton from them. That
         | might just be the best you can hope for with this genre of
         | writing.
         | 
         | > It's both concrete and high-level.
         | 
         | Thanks! I was trying to really push the idea of having it be
         | almost a business book with code snippets. When I went through
         | college, you could either study business or study software. But
         | these days being good at both is kind of table stakes, so I
         | really feel like technical books should reflect that. But there
         | also isn't a lot of precedent here, so it was a struggle trying
         | to figure out how to actually make it work. The closest pre-
         | existing example is probably something like Clean Code, but the
         | goal of that book (as well as Pragmatic Programmer) is very
         | explicitly to make developers better at programming, rather
         | than making your business more successful. Which is a subtle
         | difference, but it actually leads to both the specific advice
         | and the larger structure of the work being very different.
        
           | travisjungroth wrote:
           | Same on Two Scoops. It was incredibly useful when I was a
           | junior dev in my first job doing Django. So often it's like
           | "there's five ways I can imagine doing this. What's one that
           | at least doesn't totally suck?". It was nice having an
           | opinionated guide for that. I thought I put it in the giant
           | beginner Python/Django syllabus I made[0], but I guess not.
           | 
           | Reading your guide more in depth, I really wish I could send
           | it back in time to myself. A weekend going through this would
           | have up-leveled me by a year. I don't know, maybe it would
           | have just caused me more headache. The app I was working on
           | was definitely following a lot of _not_ best practices, and
           | seeing even more might have just been too much!
           | 
           | [0]https://www.reddit.com/r/learnprogramming/comments/i9vuhr/
           | i_...
        
         | valbaca wrote:
         | > 1. There's a bunch of things a I disagree with in here.
         | 
         | Can you share?
        
           | travisjungroth wrote:
           | The core thing is that I haven't been a fan of the services
           | layer described here. I'm more of the opinion that if you're
           | going to use Django, use it all the way. Fat models and all
           | that.
           | 
           | But, when I read more thoroughly, the guide makes a really
           | compelling case. I've generally scoffed at the idea that you
           | shouldn't couple your business logic to Django in case you
           | want to swap out Django for something else. It's hard to
           | imagine a realistic situation where that doesn't lead to a
           | total rewrite anyway. But putting business logic in functions
           | for readability and testability makes sense to me.
           | 
           | Other disagreements flow from that same one. Not testing your
           | models much being an obvious one.
           | 
           | Not a fan of Hungarian notation. I've had a better experience
           | with type hinting than the author.
           | 
           | I don't share the disdain for URL parameters.
           | 
           | From my first quick skim, that felt like a lot. But I really
           | do agree with much more in this than I disagree with (one big
           | app to start, small serializers, good function calls, unique
           | names and lots more). And the things I do disagree with, this
           | guide has me reconsidering (except type hinting. I love that
           | shit). I mostly wanted to point out that even if I don't
           | agree with everything written there, I think it's very well
           | written.
        
             | dd82 wrote:
             | James Bennet is of the same opinion as you re service
             | layers
             | 
             | https://www.b-list.org/weblog/2020/mar/16/no-service/
             | https://www.b-list.org/weblog/2020/mar/23/still-no-service/
        
             | mands wrote:
             | I've gone back and forth on fat models vs service issues
             | myself - I've landed on a bit of both in the end, but it's
             | made harder by DRF not really recommending the fat model
             | approach (with respect to validation).
        
               | travisjungroth wrote:
               | Isn't it amazing we're still all poking around how to
               | best represent business logic in software when this has
               | been the whole damn point of what we're doing for the
               | last 30 years at least? We got pretty good at the pure
               | computing stuff pretty quick, but like hell we can all
               | agree on how to represent that a food purchase over $100
               | by a user in Wyoming needs a 5% tax added.
        
               | Daishiman wrote:
               | The problem is that different levels of granularity imply
               | enormous differences in representation.
               | 
               | If you operate in a single jurisdiction and sell just a
               | coupld of SKUs, handling tax can be a 20-line affair. On
               | the other end you have SAP multi-million line ERPs that
               | handle tax as far as the eye can see and you need literal
               | specialists just for that.
        
               | lifeisstillgood wrote:
               | Yes.
               | 
               | That has touched on something deeply important. I wish i
               | knew what :-)
               | 
               | My 2c is "software literacy". We live in a literate world
               | so we all more or less can read the sentence "food
               | purchase over $100 by a user in Wyoming needs a 5% tax
               | added." and understand it.
               | 
               | But 98% of people could not scan the same in C, C++,
               | Python etc.
               | 
               | And 98% of those of us that could wont understand the
               | _context_ the code sits in (whats the variable name
               | 'WY_TAX_PCAGE'?)
        
             | danjac wrote:
             | I generally use "services" when there's a glob of logic
             | that works with a number of models and other things. For
             | example, you might have some order checkout logic that has
             | to write to a number of database tables, send email and
             | other notifications, and ping a third party service or two.
             | You don't want to tie all that logic to a single model as
             | it crosses a number of boundaries, and you don't
             | necessarily want it inside a view, as it might be run in
             | some other context such as the command line, or perhaps run
             | from different views.
             | 
             | On the other hand, if there is logic that is neatly
             | encapsulated within a model instance or queryset, I don't
             | see a good reason to artificially make a service for it.
             | Generally refactoring into services is something I do when
             | it's obvious what their responsibilities and boundaries
             | are.
        
           | maxmorlocke wrote:
           | As a startup CTO and heavy django user, I'd agree with the
           | general assessment - there are some things I don't like
           | stylistically... but that's style. Everything in here is well
           | reasoned and systematically covered. Kudos to you and your
           | team for thinking this through and sharing it.
           | 
           | As an example of some nitpicks: * I never interact with the
           | raw request data, I pass that through to a form or serializer
           | for validation. It handles the null/blank string based on my
           | configuration. In fairness to your approach, your code is
           | much more readable and will be substantially easier for
           | someone who is new to Django to pick up. * I use mixins to
           | handle fat models, while also pushing validation logic into
           | serializers. * I separate out all my files and prefer to have
           | small, atomic files that are easily testable (e.g. models,
           | views, etc. are all micro-sized files). * args/kwargs is
           | super handy when doing something really simple, then calling
           | super. I feel like this is an exception that should always be
           | allowed, but otherwise strongly agree. * I bias towards class
           | use, even though I agree there's no real need. I find this to
           | be a good convenience for new developers coming aboard.
           | 
           | I want to reiterate that I feel like these are nitpicks /
           | minor disagreements of compromises. Using a document like
           | this as a guidepost is awesome for a new team kickstarting
           | with Django - better to have a reasonable, well thought out
           | set of rules codified than to have a whole hodgepodge of
           | design decisions.
        
           | mtberatwork wrote:
           | On the point of classes and functions, it doesn't
           | specifically address "function based views" and "class based
           | views" of Django - it's mostly a tirade on OOP in Python. The
           | majority of the Django world has long switched to class based
           | views.
        
       | theptip wrote:
       | Lots of good wisdom in here, which is clearly hard-won. I'm
       | seeing a lot of common themes with the pain points that I've
       | found working with Django over the years, and while I agree with
       | (or at least don't disagree with) the majority of these, in some
       | cases I've gone with different solutions:
       | 
       | 1. I agree with the recommendation to decouple serializers from
       | models, although I haven't finished this migration in my current
       | codebase. In practice all of our ModelSerializers override many
       | field definitions, and have as much boilerplate as if we built
       | them from scratch. However the ModelSerializer approach is really
       | productive for the first stages of your product when you're just
       | cranking out CRUD views. (Maybe this is a trap.)
       | 
       | 2. I've also found "Fat models" to be quite painful in Django.
       | Fat services is probably the most obvious approach to solving
       | this, and it's what we evolved into. Another approach I've been
       | exploring is going full DDD (i.e. your Domain Models are not
       | ActiveRecord / Django DB models).
       | 
       | A common concern I have with both of these approaches is: if
       | you're eschewing the Model<>Serializer linkage, and you're also
       | trying to treat your ORM models as dumb data mappers instead of
       | fat models with logic, then what are you gaining from a
       | heavyweight framework like Django? Wouldn't you be better with
       | FastAPI/Flask using Marshmallow for the API, and SQLAlchemy for
       | the ORM? The main answer I come back to is "Django Admin" but
       | with things like Forest Admin and hopefully one day an OpenAPI
       | admin from FastAPI, this benefit may evaporate too.
       | 
       | 3. Regarding "Hungarian notation", I'm a bit dubious about using
       | `_list`, `_set`, etc in variable names; type hints are working
       | out better for me in this area. Prior to adding type hints I did
       | have some coding standards around explicitly suffixing functions
       | that return QuerySets, because this is the one case I think you
       | actually need to care about when reading code -- if you mistake a
       | string for a list, your tests will fail. If you mistake a QS for
       | a list, your code will pass, but you might accidentally trigger
       | O(N) DB lookups where you should have O(1).
       | 
       | 4. RE: integration tests on views, my codebase is structured
       | similarly, and I prefer to target services. You should have tests
       | on your views too of course, but I think it's more ergonomic to
       | drive your business logic tests at the service layer, without the
       | DRF API machinery in between you and your code under test (i.e.
       | so you see the actual domain exceptions, and not just the API
       | response codes). We get view coverage by layering some happy-path
       | API tests on top, and a small number of long-form scenario-based
       | integration tests for full workflows using a discursive black-box
       | API testing style.
       | 
       | If your domain is simple, then view-based integration tests
       | probably suffice. It's probably where I'd start for a small
       | project now. Operating in a fairly complex domain model, we write
       | lots of UTs around complex business domain rules. "Test at the
       | lowest level that lets you clearly describe the feature you are
       | testing" is my general approach. In my experience the view layer
       | is pretty boring, and we seldom find any interesting bugs there,
       | so I don't feel the need to spend much time testing it (it's just
       | boilerplate). The domain/service layer is where all our bugs tend
       | to be. If you trust that your service layer is well-tested, then
       | your view-tests can be quite simplistic.
       | 
       | I'd note that this point is also vulnerable to the fuzziness of
       | what actually demarcates UT vs. IT, and I think that definition
       | probably drives a lot of the differences in which slogan folks
       | advocate here. I definitely don't go with the "UTs cannot touch
       | the DB" approach, though I'd like to experiment in that
       | direction.
       | 
       | Perhaps from the author's rigorous definition of API error codes,
       | the response to this point would be something like "your API
       | error responses should be rich enough that you can debug all your
       | integration tests with them". I can see that might be a good line
       | to draw in the sand.
        
       | pier25 wrote:
       | > _Most existing advice on software architecture is written for
       | $10B+ companies, and as such tends to focus on maximizing things
       | like performance, scalability, availability, reliability, etc._
       | 
       | Definitely.
       | 
       | I'd argue GraphQL is a good example of this as it solves issues
       | that only very large teams/apps have.
        
       | SilurianWenlock wrote:
       | Does anyone else find django-rest really confusing?
        
         | aiisahik wrote:
         | Your experience with DRF depends on
         | 
         | 1. started hello world app: wow magic!
         | 
         | 2. doing real world situations with slightly weird scenarios:
         | This is very confusing. Why did they make this so complicated.
         | 
         | 3. after digging into the source code and reading the docs: Oh
         | this makes sense after all. I'm going to extend it where need
         | be but overall stick to the pattern
         | 
         | 4. after hiring 5 engineers to work on the same code base: DRF
         | is the bible and and shall heap scorn all MRs that deviate from
         | its mantras.
        
           | Daishiman wrote:
           | Can confirm.
           | 
           | I have started building a new product. I used standard
           | serializers for everything I can get away with.
           | 
           | If I hit a pain point with a serializer that I can't solve in
           | 10 minutes I drop back to manual serialization methods and
           | leave it at that.
           | 
           | When I have to tidy up or bring more people we try to do
           | everything by the book because, as you said, there's a lot of
           | very good reasons why REST framework is structured the way it
           | is and the predictability it brings is worth the cost of
           | dealing with edge-case quirkiness.
        
         | Xavdidtheshadow wrote:
         | IMO, Django Rest Framework has too much magic for my tastes. I
         | haven't spent much time with it (which is maybe why it's so
         | confusing), but it always seems like I need to set certain
         | properties and then it "just works"; I haven't found a good
         | list of those magic properties.
        
           | Daishiman wrote:
           | The reality is that DRF documentation can fail at the edges
           | because it's a big framework, however it's ridiculously well
           | documented compared to 95% of frameworks or libraries I've
           | used.
           | 
           | For the edge cases, you can read the source code; it's very
           | clear and well commented. And if you fail to understand a
           | feature, it's relatively easy to drop to a lower level of
           | abstraction without losing too much.
           | 
           | Ex: don't like nested serializers in a read-only method? Just
           | write a serializer method and return your own JSON. Can't
           | grok ViewSets with mixins? Just use a regular API view and
           | override what you need. Don't like routers? Use regular
           | views, and so on.
        
         | Alex3917 wrote:
         | It is pretty confusing, which is why I advocate using only the
         | core pieces of it. This way you get basically all of the
         | benefits, but without each new developer needing to read
         | through hundreds of pages of extra documentation to try to
         | understand lots of random design patterns that might not even
         | be a good idea in the first place.
         | 
         | When I've created style guides for startups, I've been pretty
         | explicit about saying "We use REST framework, but here are all
         | the pieces we don't use, and these are all of the sections of
         | documentation that you don't need to read or understand."
        
           | jtdev wrote:
           | At that point, why use Django at all? Flask seems to be a
           | much better tool for building Python based REST APIs in my
           | experience.
        
             | pphysch wrote:
             | ORM, IAM, admin interface, are all included or supported at
             | a decent enterprise-level by Django and will save a TON of
             | early dev hours and rookie mistakes. Remember, this advice
             | is geared at small organizations (startups), which means
             | there is plenty of backend and internal infrastructure
             | required.
        
             | dd82 wrote:
             | In my experience, most CRUD flask apps end up replicating
             | 80-90% of django projects with 200-300% of the effort.
             | Heck, getting pytest working with flask and sqlalchemy is
             | still a struggle.
        
         | jtdev wrote:
         | Yes, I also found the Django ORM to be absolutely horrendous.
         | The need for using a serializer was also incredibly offputting
         | and the default behavior of said serializers was almost always
         | unhelpful and in fact caused more work than just crafting a
         | JSON (or XML) object as I would do in Flask. Why some
         | developers prefer these awful abstractions is beyond me.
        
           | PhoenixReborn wrote:
           | The ORM works pretty well as long as you only need to do
           | pretty basic CRUD queries, though I do agree that it gets
           | much worse as your queries get more complex.
           | 
           | Serializers are definitely one of the worst parts of DRF.
           | Anecdotally, I used Pydantic (instead of Marshmallow as the
           | author recommends) to get around this.
        
             | ensignavenger wrote:
             | I use pydantic for serializers in Django too- and recently
             | started experimenting with Django Ninja https://django-
             | ninja.rest-framework.com/ if you haven't seen it already.
        
         | SheinhardtWigCo wrote:
         | The learning curve is steeper than a lot of stuff in the Django
         | ecosystem but I felt the payoff was well worth it. It's a bit
         | like Django itself: maximally decomposable and expressive, so
         | much so that the "right" way to do something can be unclear at
         | first. I found it hard to wrap my head around the division of
         | labor between serializers, views and viewsets, but it
         | eventually made sense and now I see the elegance in it.
        
       | codeptualize wrote:
       | The article is very nice, some great advice, and like others said
       | it's not just relevant to Django.
       | 
       | One part I don't agree with is about frontend. Frameworks have
       | stabilised quite a bit, pick React or Angular and you can expect
       | support for years to come. I still maintain a 5+ year old React
       | frontend with lots of logic, updating hasn't been an issue at all
       | (codemods), since we moved it to CRA it has been even easier.
       | 
       | Things like formatting dates: you can wrap it in your own
       | function then it takes a few minutes to swap the library, but you
       | likely won't have to change it if you follow the tips in this
       | article about dependencies, which are very good!
       | 
       | Anyway, great article, very good advice. I think it does a good
       | job of explaining that you should think about why you do things
       | and focus on what matters and makes a difference.
        
       | exdsq wrote:
       | Just started building my SaaS startup with Python (FastAPI,
       | Pydantic, postgres). Any tips on alternative stacks if they can
       | massively impact productivity?
        
         | davidkell wrote:
         | We recently started working with "traditional" Django + Hotwire
         | + React wrapped into web components for interactivity.
         | 
         | We've previously tried React+Hasura/Postgraphile and
         | React+DRF/GraphQL, but this is next level productive and a joy
         | to use, and bugs are rare. Particular love for Django CBVs.
        
         | marton78 wrote:
         | I've had great experience with Postgraphile. It creates a
         | GraphQL API by inspecting your Postgres database schema.
        
         | travisjungroth wrote:
         | The biggest productivity impact is not switching frameworks.
         | That out of the way, Django is the OG batteries included (DRF
         | is top API choice), Flask is the OG micro-framework, FastAPI is
         | the new hotness, Starlette is in FastAPI (and maybe better?),
         | Pyramid has some cool ideas, and then there are more that I
         | can't say anything on (Bottle, Falcon, Sanic, Hug).
        
           | holler wrote:
           | I'm using Starlette on it's own, with a few other deps likes
           | Marshmallow, and it's been enjoyable and hassle-free. In the
           | past I've done significant django/flask/DRF work and really
           | feel like Starlette is the successor of all of them.
        
           | exdsq wrote:
           | > The biggest productivity impact is not switching frameworks
           | 
           | Totally agree but I'm 2/3 days in with 500 loc - it's worth
           | rebooting if there's something seriously good out there!
        
       | davidkell wrote:
       | As the sibling comment says, while I disagree with specifics this
       | article is full of gems.
       | 
       | @Alex3917 What do you think of using Django-rendered templates
       | for views that don't require significant frontend interactivity?
       | 
       | You seem to advocate the "everything is an SPA + Rest API"
       | approach, which I thought was interesting.
        
         | Alex3917 wrote:
         | > I was surprised that you didn't advocate for using
         | traditional server-rendered HTML for views that don't require
         | significant frontend interactivity/client-side state
         | management?
         | 
         | I don't know that there's one best choice for how to render the
         | front end. I generally like Angular because it's strongly
         | opinionated, and in general, strong opinions in software leads
         | to faster velocity and lower TCO. But I don't think that
         | Angular is the universal best choice. I do however think having
         | the front end powered by a REST API is 100% the right move,
         | because it draws a line in the sand where if private data or
         | incorrect data are getting returned then there is clearly an
         | issue. Having a specific place (the JSON response) where you
         | can write tests against is a huge win, one big enough that imho
         | it outweighs every possible disadvantage of REST.
        
           | davidkell wrote:
           | OK, that's an interesting POV.
           | 
           | To be honest, I was surprised that you advocated writing so
           | many tests for each view, easily 10-20 per view.
           | 
           | Part of the beauty of Django's various class based interfaces
           | is that you can be confident that if you add a validator to a
           | model field, then it will be validated by the corresponding
           | ModelForm in the corresponding CreateView.
           | 
           | But then again, you advocate against OOP in python including
           | CBVs. If you do write all your endpoints as functions, it
           | makes more sense you need to test it because it's easy to
           | forget to include a line of validation or whatever it is.
           | 
           | FWIW I love the fact that various Django classes are like
           | DSLs. DSLs are less powerful by construction, so less buggy.
           | It is almost like using low-code. But I do see your point
           | about tracking control flow and it can be a challenge at
           | scale.
        
       | etaioinshrdlu wrote:
       | I wrote my company's giant backend in Django, and while I don't
       | regret it, five years in the ORM still makes no sense to me for
       | more complex cases and really, really miss languages with better
       | type checking. The current options are not good enough or have no
       | library adoption.
        
       | dzonga wrote:
       | > most software architecture advice for 10bn companies.
       | 
       | I agree with this point and pointed out in a previous comment[0]
       | that as an industry, small companies lose out because they're
       | chasing a platform. And are doing things the FAANG way, which
       | they don't nee.
       | 
       | > the other way, big tech supresses innovation is by brain-
       | washing or propaganda. look at all the companies formed by former
       | faang engineers. majority of them go for vc-funding, use
       | complicated tools at the onset. not because it's a need but
       | because that's what they're used to. what would've been a simple
       | frontend is now a monorepo monster, a simple backend a mesh of
       | microservices. lastly, big tech supresses competition by open
       | sourcing tools that are technically excellent but not needed by
       | 90% of the companies out there. instead of companies innovating
       | the companies are now chasing the platform i.e trying to keep up
       | with the tools released by big tech. this is a microsoft
       | playbook.
       | 
       | like others have commented might not agree with everything but
       | that was a good read.
       | 
       | [0]: https://news.ycombinator.com/item?id=27574713
        
       | [deleted]
        
       | arickuter99 wrote:
       | Really interesting read. Wish I had read this before I built my
       | Django app. Oh well, we learn. Thanks for the awesome info.
        
       | wbc wrote:
       | This is great stuff! after working w/ various Django apps for
       | years (anywhere from 3 dev teams to 200 dev teams), it's great to
       | read stuff that confirms my biases :D
       | 
       | Regarding services, I'll go as far as to say adding ANY method on
       | models instead of handling logic in services is a recipe for
       | disaster. How many times have you seen:                 class
       | GodModel:              @property              def status(self):
       | # 1 million lines of logic and who knows how many queries
       | 
       | I've actually seen this pattern in every Django project :(
       | 
       | Regarding urls, instead of enforcing a flat file, I'd highly
       | recommend always using django_extensions[0]. You'll get
       | `shell_plus` that auto imports model and `show_urls` that you can
       | grep for endpoint and gives you the handler.
       | 
       | [0] https://github.com/django-extensions/django-extensions
        
         | piyh wrote:
         | I'm at a crossroads for a hobby project I'm working on. I have
         | a model for youtube video metadata. I have functions to do
         | things like transform the metadata into display values to be
         | rendered to template.
         | 
         | Should that logic be a function that takes a youtube metadata
         | model object, or a class method on the model to return display
         | values?
         | 
         | There are more functions of increasing complexity after this
         | display function.
        
           | Daishiman wrote:
           | I personally keep display functions away from business
           | objects. You could do with a mixin, or a helper class.
        
             | piyh wrote:
             | Thanks for the things to google, I've faked it till I made
             | it and still have some knowledge gaps to fill in.
        
           | rajin444 wrote:
           | If you're not able to quickly answer it, I'd put it where you
           | feel like you'd first look for it (where it feels natural)
           | and move on - don't try to optimize too early. The correct
           | answer is going to depend heavily on what your app is doing,
           | how it's structured, etc, so providing a good answer from an
           | HN comment is going to be really hard. It's ok to be "wrong",
           | just make sure you're consistently wrong so when you need to
           | refactor it's not so bad. Eventually the answer will become
           | clear...and if it never does, you probably made the right
           | choice and saved a lot of time :)
           | 
           | FWIW, https://www.django-rest-framework.org/api-
           | guide/serializers/ might be helpful - but DRF is also a lot
           | to take in if you're not already comfortable with Django.
        
         | blackrobot wrote:
         | Are there any good articles or examples you can share that
         | elaborate on why using services is best? Writing a custom model
         | manager method for these sorts of operations seems to work
         | best. For instance, the _create_account_ service could easily
         | be part of the _User.objects_ manager:                 class
         | UserManager(models.Manager):           def create_account(self,
         | sanitized_username: str, ...):               # the rest of the
         | code in this method is the same as the example.
         | ...               return user_model, auth_token
         | class User(models.Model):           ...           objects =
         | UserManager()              >>>
         | User.objects.create_account(sanitized_username="blackrobot",
         | ...)       (<User: blackrobot>, 'fake-auth-token:12345')
         | 
         | The benefit here is that other parts of your code only need to
         | import the _User_ model to access the manager methods. It also
         | allows for the _User.objects.create_account(...)_ method to be
         | used by related models, without risking a circular import, by
         | using the fk model 's _Model._meta.get_field(...)_ method.
         | 
         | I'm not opposed to services, I just don't see when they'd be
         | particularly useful.
        
           | olau wrote:
           | It might be a little bit more convenient, but really, models
           | are central to everything else. You're spamming your most
           | central code with arbitrary crap that you are only interested
           | in perhaps 0.1% of the time.
           | 
           | Once you get out of the OOP mentality, it's much easier to
           | shuffle code around, and keeping things that logically belong
           | together close to each other in separate files. Move the crap
           | out of the way and enjoy the cleanliness. Less mental
           | overhead helps you make better decisions faster.
           | 
           | And yes, sometimes you have to deal with a circular import,
           | but it's not the end of the world, just decide which file is
           | the most basic, and don't let that import other less basic
           | files at the top level, but only inside functions. Or try to
           | decouple the logic.
        
             | rajin444 wrote:
             | Isn't a mix of fat models and services best? Say for a user
             | model you have first name, middle, and last name. You add a
             | property "full_name" that joins those 3. Putting that logic
             | in a service feels confusing and unintuitive.
             | 
             | On the other end, if you have a complex auth mechanism that
             | needs to talk to several external APIs, putting that in a
             | service feels natural. You're making remote API calls,
             | possibly pulling in other models, and it's a clearly
             | defined "business area".
        
         | rajin444 wrote:
         | That logic has to go somewhere right? I'm not sure what the
         | issue is.
         | 
         | There's the "heuristic" of expecting a property to not be
         | expensive to access (which django already kind of throws out
         | the window depending on how you fetch the model), but otherwise
         | I don't see how services fixes this. Is copy and pasting that
         | code over to a services.py file better?
         | 
         | Services are very nice for dealing with python's import issue.
         | Accessing other models from a method / property on a model is
         | ugly. But it's very hard to structure and name services in a
         | logical way, especially when the lines start to cross. You end
         | up with a "shared_services.py".
        
           | mtoddsmith wrote:
           | Keeping the business logic in a service (aka Anemic Model in
           | Domain Driven Design terms) allows you to change the business
           | logic using different versions of the same service. You can
           | then inject the appropriate implementation for a given
           | context using IoC (Inversion of Control).
        
       | ghiaadev wrote:
       | Personally I think the MVC framework causes more overhead for
       | startups. It's like trying to shoehorn your use case into
       | complexity for nothing. Classic example of a best practice that
       | needs to be revisited
        
       | paddy_m wrote:
       | Not mentioned here but important, I strongly prefer and recommend
       | an opinionated framework like django, rails, (possibly ember)
       | over mix match frameworks (express, flask) because they have
       | already thought more about most design decisions than you want to
       | (or your engineers). Especially for an early stage startup, the
       | performance, scalability, or reliability of your stack isn't
       | going to kill you. A long time to market will kill you,
       | reinventing the wheel will kill you. When I see django apps built
       | and developed, a shockingly high amount of effort is dedicated to
       | actually solving business problems vs other less opinionated
       | frameworks.
        
         | adevx wrote:
         | You are correct about time to market, but in my experience the
         | more a framework does for you, the more friction it will give
         | when you have market validation and need to build out. Keep it
         | simple, use something like next.js, postgres, perhaps a query
         | builder like knex.js and start building. Easy to reason about,
         | massive community to get developers and support. On top of it
         | you can build it all using. TypeScript and quickly refactor and
         | itterate with confidence.
        
       | simonw wrote:
       | The title here is a bit misleading: the first few paragraphs of
       | this are a really enlightened description of the challenges of
       | engineering for a startup regardless of what framework you are
       | using.
       | 
       | Even if you have no interest in Django at all I recommend reading
       | the start of this.
       | 
       | > I wrote this guide to explain how to write software in a way
       | that maximizes the number of chances your startup has to succeed
       | -- by making it easy to maintain development velocity regardless
       | of the inevitable-but-unknowable future changes to team size,
       | developer competence and experience, product functionality, etc.
       | The idea is that, given the inherent uncertainty, startups can
       | massively increase their odds of success by putting some basic
       | systems in place to help maximize the number of ideas, features,
       | and hypotheses they can test; in other words, maximizing "lead
       | bullets," to borrow the phrase from this blog post by Ben
       | Horowitz.
        
         | Alex3917 wrote:
         | Thanks, Simon! I thought a lot about how to position this
         | piece, in terms of whether it was really about Django, or about
         | Python, or about software architecture more broadly, or
         | software architecture specifically for startups, or whether
         | it's really primarily a business book that just happens to
         | contain code snippets.
         | 
         | In some ways it's all of these things. But I think for people
         | who aren't already senior developers, it's much easier to
         | understand the advice if it comes with some specific context
         | and functioning code snippets. My hope is that by making it
         | Creative Commons (including for commercial use), people will
         | remix the book to make it work for their specific framework,
         | startup, language, etc.
         | 
         | This way people can also incorporate the sections they like
         | into their internal style guides, change them to say whatever
         | they wish I'd said instead, make online courses based on the
         | content, sell their derivative works, etc.
         | 
         | One of the main reasons it's about Python and Django (and not
         | Flask, FastAPI, Express, etc.) is because Python and Django are
         | both transparently run by non-profit foundations, and
         | development is done in the open by large communities of
         | contributors. This is a significant enough advantage that even
         | if, say, FastAPI has some technical advantages over Django on
         | any given day, over the long run it's hard to see any other
         | stack becoming a better choice for startups until both the
         | language and the framework are managed in a similar manner.
        
           | bobthebuilders wrote:
           | Exactly. FastAPI's maintainer was so unprepared he had to
           | stir up a storm to prevent a legitimate Python feature from
           | coming in. One of the benefits of a larger and more involved
           | community process is the fact that each person exerts less
           | individual control.
        
             | stavros wrote:
             | Hmm, what's the context there? I'm not familiar with the
             | FastAPI events you mention.
        
               | bobthebuilders wrote:
               | They threatened the Python language committee if they
               | didn't get their way wrt PEP 576. Their library also
               | contains C extensions with undefined behavior, and
               | telemetry that phones home.
        
               | adamzegelin wrote:
               | > Their library also contains C extensions with undefined
               | behavior, and telemetry that phones home.
               | 
               | Source for this?
        
               | jsmeaton wrote:
               | Wow this is a really uncharitable interpretation of what
               | happened.
        
               | stevesimmons wrote:
               | I don't think your choice of language helps here.
               | 
               | The reference to PEP576 here relates to some changes to
               | annotations/type hints originally planned for Python
               | 3.10.
               | 
               | For anyone interested, here is a great summary from Linux
               | Weekly News two weeks ago:
               | 
               | https://lwn.net/Articles/858576/
        
               | jt_b wrote:
               | Yeah, going to need to see some evidence and source code
               | references for allegations like this.
        
             | svaha1728 wrote:
             | Nah... That's not what happened. Please read Sebastian
             | Ramirez's post on the issue:
             | 
             | https://dev.to/tiangolo/the-future-of-fastapi-and-
             | pydantic-i...
        
           | simonw wrote:
           | That makes a lot of sense, especially given your decision to
           | use Django for code examples. I wonder if it would be worth
           | extracting out those first few paragraphs into a separate
           | article? They're really good, and applicable to way more than
           | just Django.
        
             | Alex3917 wrote:
             | It's not a bad idea. It would be cool to make a single
             | serving page for this and give it some more structure,
             | break out recommendations specific to different
             | technologies, incorporate content written by others, get
             | testimonials, etc. But it's also evergreen content, so if I
             | wait a year or two to incorporate feedback then I think it
             | will still be just as valuable.
        
       | stickyricky wrote:
       | I want to agree and disagree with a minor point. The argument
       | that error codes should be digits[0] is misguided. Errors should
       | be named. E.g.: {"invalid_username": "An invalid username was
       | provided."} is better than {"40001": "An invalid username was
       | provided."}.
       | 
       | Why? Because you'll often add coded errors far into the future or
       | multiple at the same time on different branches. When you merge
       | two errors will have the same code but totally different
       | messages. This can lead to regressions in userspace or annoying
       | merges where an error code has to be manually looked up and
       | changed.
       | 
       | Its unlikely that you'll define the "invalid_username" error code
       | more than once and if you do it will likely have a similar error
       | message. Plus, in your test suite `assert error.code ==
       | "invalid_username"` is much more explanatory than `assert
       | error.code == 40001`.
       | 
       | Just my two cents.
       | 
       | 0. https://alexkrupp.typepad.com/sensemaking/2021/06/django-
       | for...
        
         | PaulWaldman wrote:
         | How is this different than any other merge conflict?
         | 
         | Including a unique code with an error message does help with
         | SEO when users search for a solution.
        
           | stickyricky wrote:
           | Merge conflicts come with helpful characters ("<<<<<<<<") to
           | show where the conflict originated. If you merge two branches
           | (especially if they are large or reviewed on separate days),
           | its pretty easy to overlook the fact that they share an error
           | code. This will not raise a conflict in git. But is in fact a
           | merge error and users will experience a regression because of
           | it.
           | 
           | > Including a unique code with an error message does help
           | with SEO when users search for a solution.
           | 
           | Maybe. But I don't see how Googling "Product name 40001
           | error" is better than the user reading "account_deactivated",
           | understanding intuitively what happened and Googling "Product
           | name reactivate account" or "Product name account
           | deactivated".
        
         | Daishiman wrote:
         | It also helps dramatically with error internationalization if
         | you're displaying backend errors in the frontend.
        
       | pier25 wrote:
       | I'm a Node user and agree with many of the points here.
       | 
       | For the REST API, my work is 99% writing queries and business
       | logic. I use Fastify because it includes sanitization/validation
       | out of the box. I use Fauna as the data layer because it solves
       | authentication/authorization out of the box too.
       | 
       | > _URL parameters are a scam_
       | 
       | I agree with the author that URL params do not solve all
       | problems, but OTOH they do cover +90% of use cases for CRUD
       | stuff.
       | 
       | Also, most routers I know of, specify routes with URLs.
       | Defaulting to using URLs with multiple query params can get
       | pretty messy.
        
       | bosswipe wrote:
       | Having worked at two unicorn-ish startups that were founded and
       | grew up on Python stacks I specifically want to work on a
       | statically typed backend for my next job. I'm tired of the
       | mishmash that is a large mature Python codebase.
        
         | defnmacro wrote:
         | Doesn't type hinting in 3.5 solve this, assuming one was to use
         | it?
        
         | blacktriangle wrote:
         | I'm in a similar boat coming from a Rails mishmash and looking
         | at our next product, particularly debating JS vs Kotlin. While
         | the upsides of static typing are pretty clear, here's my
         | problem. All our app really does is receive a JSON request,
         | read some data from a database, do some computation (this is
         | the step where static would be nice), write some data to a
         | database, and then respond with more JSON. So the inputs and
         | outputs of the system are collections of heterogeneous maps
         | that static typing systems don't handle well. So if I want
         | those nice static guarantees in my code, I need to write layers
         | of ORM and serializer code to get data in and out of my system.
         | I'm not sure the static benefits are worth the advantages of
         | not having to maintain this additional layer of data
         | transformation.
        
           | bosswipe wrote:
           | Yep, this is where you can end up with tons of mapping
           | boilerplate and potential bugs. I tend to reach for code-
           | generation tools, schema based protocols like Protocol
           | Buffers, and prefer code simplicity over performance
           | concerns. The hope is that the price you pay for building
           | around strict consistent data models across all your systems
           | will pay off in maintainability and fewer bugs.
        
       | awaythrowact wrote:
       | Learned a bunch from the article and from this thread. Thanks
       | all.
        
       | dopeboy wrote:
       | As I'm reading the article and the discussion here, I'm seeing
       | mostly talked around REST. How are folks supporting a GraphQL
       | interface with Django? Also,
       | 
       | > Rule #17: Keep logic out of the front end
       | 
       | I'm curious how folks who use a db-to-api tool (e.g. Hasura)
       | think of this? I'm currently working on a codebase that has
       | Hasura and we're doing, what seems to me, a massive amount of
       | logic in React. But as far as I know, there's no alternative
       | under that scheme.
        
         | lovasoa wrote:
         | We are doing the same, and are quite happy with it. This means
         | that very often, implementing a new feature can be done
         | exclusively on the frontend, as opposed to spending time on
         | both the frontend and the backend as we would with the old-
         | school approach.
        
         | tango12 wrote:
         | (from hasura)
         | 
         | With Hasura, if you need want some server-side logic to be
         | written away as an API, you could try 2 approaches:
         | 
         | 1. Write a REST/GraphQL API in your favourite framework and
         | bring them in as Actions/Remote-Schemas in Hasura. Hasura will
         | add them to the GraphQL API. 2. If you like, and if its
         | possible, you can also abstract away logic in database
         | functions and then Hasura will expose them.
        
       | d3ntb3ev1l wrote:
       | Lord helps us, I've inherited so many django/drf projects from
       | startups and would rather eat poo than fix them again.
        
       | esjeon wrote:
       | I love this guide, because the company I'm working for is
       | seriously suffering software quality issues due to lack of proper
       | /modern/ doctrine. The project was driven by a Java guy who
       | didn't study Python+Django even the slightest. I think I should
       | really share this with my colleagues.
       | 
       | > Rule #11: URL parameters are a scam
       | 
       | I think this section is going a bit too far. The examples are
       | inappropriate.
       | 
       | First of all, you should not use hierarchical URLs for APIs.
       | There's a good API design guide from MS[1], which discourages
       | embedding resource hierarchy into API URL structure. Always
       | design APIs around resources, and resource hierarchy is not a
       | part of resource obviously.
       | 
       | Also, `GET /clothing/shirt/<str:shirt_id>/pants/<str:pants_id>/`
       | is a completely invalid example for bashing URL parameters. This
       | is clearly a _search_ operation, and search queries SHOULD be
       | passed as GET query parameters in the first place.
       | 
       | I also personally discourage toggling output using GET flags,
       | because it kills caching. Just try to minimize the number of
       | representations for each resource, and this will naturally reduce
       | the number of endpoints and optional flags. If an attribute is
       | big, remove it from the main representation and serve it through
       | a dedicated endpoint.
       | 
       | [1]: https://docs.microsoft.com/en-us/azure/architecture/best-
       | pra...
       | 
       | > Rule #18: Don't break core dependencies
       | 
       | Previously, our company tried to migrate an old service to Django
       | + Google Datastore (w/ 3rd party library) + external access
       | control framework. You see, it didn't survive long, and we had to
       | start a new project. The attempt only increased the number of
       | legacy systems, lol.
       | 
       | ...
       | 
       | Apart from what's written here, there are a lot more ways to f**
       | up the system. I know, one guide can't simply cover all possible
       | cases, but I want you to know that the world is big and the
       | possibility is, of course, infinite.
        
       ___________________________________________________________________
       (page generated 2021-06-23 23:00 UTC)