Hacker News new | ask | show | jobs
by rlawson 1653 days ago
Django has allowed me to enjoy some side entrepreneurship. I have released three products as a solo part time dev that I would never have been able to do in a reasonable time using Java/Spring (my strongest stack). My first project went nowhere, but the second generated 1k+ a month and sold for 50k, and the third one is following a similar trajectory.

My advice - keep it simple

- function based views

- centralize access to the ORM (avoid fat models, use a service layer)

- responsive bootstrap template with crispy forms

- minimal js

- 12 factor type setup - django-environ or similar

- be aware of n+1 select problem (I use django debug toolbar but looking into django-zen-queries)

- plop it on heroku, AWS lightsail, Digital Ocean or if containerizing use AWS Fargate

- avoid celery unless you are at a scale you need it - use django-cron, django-db-queue or similar

- use a managed database and managed email service

I have had zero scalability problems. People say Django is slow but it handled 10k active users on a small EC2 instance without any issues (static stuff served via cloudfront)

13 comments

> function based views

Class-based views, in their most basic form, are a lot easier to read. E.g. look at the DRF CBVs I have here:

https://github.com/Alex3917/django_for_startups/blob/main/dj...

If you can avoid Generic CBVs (things like ListView) and inheritance, then the only difference between FBVs and CBVs is that CBVs make it easier to see what's a GET / PUT / POST / DELETE by adding some syntax highlighting that makes it easier to visually differentiate which code goes to which method. You don't need to know anything about classes in Python in order to use them.

It's not at all difficult to switch from FBVs to CBVs later, and most people (myself included) use FBVs when getting started. But I'd also say that if you're willing to push through the initial discomfort and spend the extra half hour or whatever on YouTube in order to understand them, then you do get a little bit of a nicer overall development experience.

Alex has written an excellent blog about Django best practices for startups. It contains wonderful insights about how you should structure code and why you should structure it that way. It has helped us a ton! https://news.ycombinator.com/item?id=27605052
The blog post itself is also archived here: http://archive.today/1cNZb
I usually have a FBV handler (foo_handler) that just passes along all the required parameters to foo_get, foo_post, etc, and then takes the return value of those and wraps it in a HttpResponse/JsonResponse.

That way my actual view logic doesn't have to deal with anything http, and it makes testing them way simpler.

Class views are fine. Even just a TemplateView saves some effort, but it comes with learning some Django magic about the life cycle of the view to load data in and out and what methods to implement. I still recommend it. You don’t have to fully embrace it and it lets you avoid repeating a lot of code.
My recommendation for learning class-based views is to use a good editor or IDE that allows you to drill down into the library code easily. In PyCharm/IntelliJ I can just command-click on the TemplateView symbol and see its implementation, and do so recursively until I've unraveled the entire thing.
Agreed. Django's documentation is pretty light on the order in which certain functions will be executed and diving into the code is the only way I've been able to understand what happens when handling forms and views.
http://ccbv.co.uk/ is a pretty useful resource for learning class based views...
>avoid fat models, use a service layer

I had not heard the term "fat models" before so I googled it. It did not go in the direction I expected.

Another classic is "c string".
Every time I need to write up a research paper in TeX and insert a graph, I make the same mistake of searching "latex images" and getting some pretty exotic results
I recall a colleague googling Prince Albert in front of his boss whilst discussing a potential project in Monaco. That was before either before safe search was a thing or he had it switched off.
As a construction engineer, content with the word "erection" is very common but not very easy to get by the filters.
Another classic was (I haven't tested recently) that while Firefox would search if you entered a word that was not an address into the adress bar, while certain older browsers, at least Safari just added .com

:-/

I remember that, specifically Python dot com. (was NSFW back in the day, enter at your own risk).
I remember studying fork/exec at uni and having some awkward queries involving “fork”, “kill” and “child”.
I just got results for repairing cellos and violas. "G string" got the expected spicy content in addition to violin repair ads. Surprisingly, no string bass repairs (in spite of the bass also having a g-string)
When I first started using the python tool/ library named fabric, I knew that putting just "fabric" into google would not get me what I wanted, so I instead searched for "python fabric". The top results were trying to sell me handbags and boots.

(These days, the library does rank first for "python fabric").

And when you need to look up the Typescript playground, it's probably best not to abbreviate typescript as "TS".
"mongoose relationships" is not always what you would expect, either.
I learned something new today. And not about strings in C.
Yeah, I think "deep model" (versus shallow) a more accurate and less punny phrase.
I think the idiom was: Fat models, skinny controllers. That might return better results.
I laughed
It's great for getting things up and running. And can last a long time. But now that we're 40 devs or so working in the same 400k LOC codebase, I'd prefer Java/Spring (or really, Kotlin). So hard to maintain django in the long run, need to be really strict, or one ends up with each app spaghettied with other apps. Doing queries where you filter deep on other apps' models, and since it's only done as kwargs with no typing nothing stops that from exploding runtime when someone changes a model somewhere. Too easy to send fat objects around everywhere, accidentally doing heavy db stuff when using a property. Also makes it harder to test, because everything leaks.
Strongly agreed on these pain points. One tool I have been using recently to help is django-seal, which locks the QuerySet so you can’t run extra queries. This should really be part of the ORM, fail-unsafe is a bad option for performance critical code.

Combined with the Repository pattern from DDD, you can have all your model fetches go through a separate class that does the necessary select-/prefetch-related calls, then seals. (Or just override your model's Manager to do this).

And we have a coding standard guideline to discourage using queryset operators in business logic, as you note it breaks encapsulation and makes your code really hard to refactor later. This is hard to enforce though…

I think Django’s ORM is if anything too convenient - it’s great for the first 100kloc but then as you say, you need to overlay some discipline to prevent things from blowing up, and the framework is all about removing friction which makes this hard.

I can see that happening on a large codebase with a lot of devs. Being a Java guy, I organized all the business logic and db access into a service layer, added type info and wrote a decent amount of unit tests. I can't deny however that I am 2x faster cranking out crud screens in Django than in Java and the combination of ORM, migrations, templates, and access to the 3rd party app ecosystem is a real productivity booster.
Yeah, for a simple app, it's so quick to get some auth, rest endpoints, a fine admin panel etc.

Problem is when you start abusing stuff. Just hook into some signal to do something, nbd, but suddenly it's an interconnected mess. Just add some custom stuff to the admin page, and suddenly what should have been a custom made page is now a weird mess it's hard to extend. Need to do stuff async in the background, and what could have been adding a task on some queue and having a thread poll it is instead this behemoth of complexity.

A large django app can also be good, I think, but then one at some part have to realize when to stop tweaking built in django features and write custom stuff, to avoid adding hacks on top of hacks. And also early stop doing the easy communication between apps, instead take the annoying detour around services and hard boundaries.

Yes, but nothing is stopping you from typing and organizing everything, right? I agree that it's not the default, which isn't ideal, but you can do it fairly easily (and keep it enforced).
Problem is that the typing with mypy is in general not a very strong guarantee in Python, and combined with Django most of it's almost useless. Sooo many kwargs doing magic stuff, or types being lost after being through some django functions.

And hard to do the organization propably in django, doing it breaks much of the benefits of the orm for instance. People will do what's convenient, and in Django that's writing unmaintainable code.

This seems like an architecture issue, rather than a framework issue. At some point you need to split these 400K and 40 devs out into different applications. Different application should not be able to access each other's database, but rather communicate through an API.

I'm not saying that you need micro services, but you should find a way to split this up into a few applications with well defined interfaces between the applications.

I agree. What we've been doing is - based on advice from somewhere I can't remember - is to always make things easy to remove. So whenever a new app/module/service is added - the "author" has to think about making said thing easy to remove. The result is that new apps/modules/services must have very few connections with the other parts of the code. It does result in a fair amount of "get_<app>_settings" on the base classes, but the result is a lot less spaghetti. The thing is, we rarely remove stuff later on - but when we do, it's often only a few methods that needs to be altered in order to disconnect a specific part of the system.
> one ends up with each app spaghettied with other apps

This is what I always found in Django projects with the notable exception of the one that had only one giant app.

I started to believe that one project doesn't get along with multiple apps (kind of microservices) in the mind of the average developer. Only one app Rails style is probably easier to grasp and manage.

I think CBVs get a lot of unnecessary hate. DetailView, ListView, DeleteView, etc save so much time and repetition for basic CRUD pages. Once things get complicated and you start having to override too many methods, it's often best to write the post(), get(), put(), etc methods explicitly. At that point you are still writing CBVs but it's much more organized.
They are alive and well when using Django Rest Framework
I’ve found managed databases to be quite expensive at places like Azure and AWS. Especially compared to just installing pgsql on raw compute.

What managed databases do you recommend that adequately meet price/performance needs of indie developers?

RDS - Aurora/Postgres flavor - small instance. Entire AWS bill was < $100/month to support 10k users - app load balancer

- WAF rules

- EC2 instance

- RDS

- Cloudfront

I use fargate when deploying now to get out of box management

PaaS and managed services let you trade money for time and effort - patch management, backups, security, config optimization, simplified scale-out/availability/etc. all take time, effort and skill to get right and keep running well over time. With RDS/Aurora/AzureSql/CosmosDb (and the like) you get to focus on your app/service/product instead. It all ends up being a question of how much your time (and being able to focus) are worth to you. If this is an actual business and not a hobby, and assuming market rates for your time - I believe the managed service offering ends up the cheaper option overall.
Digital ocean also offers managed db(postgres/mysql) starting at $15/month
I've used ElephantSQL before. As long as you write efficient queries (especially avoiding N+1) and choose the right data centre it's usually reasonable for a small project.
Ideally try to avoid using Celery at all. It's the most consistently buggy, poorly documented, worst quality major Python project I have used so far.

Here is a fresh example: they deprecated CELERY_ prefixes in Django settings for some reason, which makes zero sense [1]. But because it's Celery, they only warn on a few properties but not others, and the tool they ship inexplicably renamed completely unrelated settings for me [2]. And yes, apparently the new way doesn't work either [3].

It's very characteristic of what I've seen from Celery over the last few years of working with it. If you are doing anything greenfield, it's best to avoid it altogether.

[1] https://stackoverflow.com/questions/65554242/celery-imports-...

[2] https://twitter.com/lambdadmitry/status/1468337594358546435?...

[3] https://github.com/celery/celery/issues/7140

I recommend this post when thinking about fat models/service layers

https://www.b-list.org/weblog/2020/mar/16/no-service/

May I ask what the projects were?
Car reservation system for auto dealers (never got traction)

Covid screening application (sold)

Inventory management (initial clients in alcohol beverage bottling/distribution)

Do you have any recommended readings/resources to help with data modelling? Especially regarding reservation/scheduling and inventory management?
The reservation app was super simple so no real guidance there I'd recommend really getting familiar with the Django ORM so that you understand how it generates the schema and how to tweak it to get what you want.

As far as inventory management - I looked at several opensource projects and tried to understand their schema and use cases. Not just Django ones but other languages like PHP - PartKeepr for example.

Inventory management is surprisingly complicated to do in a generic way.

The most difficult part of inventory management is convincing employees that telling the system that 40000 units moved when only 38732 units moved is a problem
Thanks for the pointers! Yeah generalizing inventory management is very confusing. I tried looking into Oodo but can't get my head around the data modeling. It feels like trying to implement double-entry accounting, but the money can rot/be broken/gone for other reasons.
Congrats on making a return on your investment! may I ask how you went about locating a buyer for the screener app?
Asked one of the local investors and got steered to another company that was in the same business altho much bigger. Really just plain luck and I guess having a decent network.
May I ask how you discovered those two profitable niches? Did you already have contacts working there that just told you about it or did you go about hunting it down somehow?
+1 for trying to avoid celery. Too much complexity for simple asynchronous tasks. You should use it however if you are instagram (I think they use a customized Django and Celery).
I’m sorry but I don’t buy that. Spring Boot is very highly productive — I really don’t believe any other stack would fare better by a significant margin.
You have to type and read a lot more code in Spring to get equivalent functionality.

Also, and it's hard to believe, but Spring has substantially more magic than Django.

Fair enough, I'm just a datapoint of one. YMMV
Did you ever have to use Vue or other reactive frameworks with Django? Do you use DRF at all or just mostly Django?
Plain Django with html templates and some htmx/unpoly for fanciness
Hotwire works very well too.
please point us at your side projects - and congratulations that's a fantastic outcome
The one that you sold… what made you decide to sell, if it was a money maker for you?
Basically I didn't want to support it. It was a lot of schools and small businesses and so plenty of "I can't login" and when you work with them it's their wifi or something. The company I sold it too had customer support :)