[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)