Question from a mediocre programmer: what are the advantages of this over, say, a messaging queue and microservice architecture for an application (i.e. Web service built on flask, but the details don't really matter)
For example, would it be a bad idea to put my email module on an async function as opposed to in a different application?
They aren't really meant to accomplish the same goals, async and microservices. What I mean is, these are architectural choices. While at some level they may look interchangeable, they really aren't.
There's no 100% correct way to say when to use one or the other, but here's one way I would think of them.
Microservices are used to help define the architecture of your system overall. This is more ... high level.
Async would be more at a process level.
So, for your example, you could have a flask microservice that exposed an interface that accepted a list of email addresses and a message to send to each. The request handler for that could then call a method using asyncio to send the emails. This would allow the microservice to remain responsive and wait for more requests.
You're asking how modular your system should be. That's like asking whether a 20-line function should be split in two. It depends. Is the code easy to understand?
Yes, it is easy to understand (and even more than that - yes it can be in its own module/blueprint anyway). My previous thinking has been that if I have a message queue and separate application then I get the benefits of failure reporting through the queue as opposed to potentially missing something if I get a failure through async. Although I guess they aren't insurmountable.
Then, inevitably, you have growth so that one email function it may begin with, but later you're doing other fancy things and even though you may just want to send a single email at the start, later you're making database calls and doing other bits and pieces to make the template more fancy and personalised etc. So, better to start the architecture from the start in a slightly more complicated fashion..?
No. Start with "bad" architecture and refactor as needed.
Another layer of abstraction solves any problem. If you don't have a problem yet, then abstraction is unnecessary complexity.
BTW, writing your own async code may actually be more complicated than just having a message queue and an email program. It depends on how much trouble your message queue is giving you. Can you use a Sqlite table instead of a "real" message queue?
I'd have the web-server append "messages" to a sqlite table and have the emailer read from the same table. You can use cron to run the emailer every X minutes. If the table gets huge, use cron to run an archiver that prunes old messages during low-usage hours. Sure, the pruning will temporarily lock the database. If that gets to be an issue, switch from SQLite to PostgreSQL. If that architecture starts to fail, that's a good problem to have.
You could argue that writing to the queue should be done asynchronously inside the web-server, but, again, wait until your customers complain that the server isn't responding quickly enough.
In some circles, this idea is known as "incremental architecture". The idea is that you should architect and add abstraction layers to solve problems you have /today/, rather than solving problems in the future.
One big reason for this is because we're often wrong about what problems we'll have in the future, or in what way they will manifest. Moreover, often the "bad" solution will work for far longer than we expect, and the day that we're anticipating where we'll be having problem X might never even come.
What's worse is that in the meantime each layer of abstraction adds a tax to how easy it is to fully understand and work with the code, to change things and add new things.
What you do want to do is to write code that is /open to/ being abstracted. E.g. keep things structured nicely, start with a basic function (named similarly) or a few related functions before putting in a full blown class, name things well so it's easy to find them while refactoring, etc.
I've done this with iteration 2 (current version) or my backend but now I'm not a one man shop and have a team (well one other junior backend and a senior mobile). And we are architecting for some pretty major new features so it's interesting trying to strike a balance with both what I've learned along the way and what it seems 'best practice' is when it comes to python
Careful with where you read "best practice" from. Some folks like to brag about their new high-scalability architecture, but it's way over-engineered for most software.
The pattern I described of using SQLite tables to mimic a message queue will be very easy to switch to PostgreSQL or MySQL. If you hit serious scale you can switch to something like Kafka or Kinesis. That'll be a bit more involved, but hopefully your revenue and team scaled to match the compute needs. When you switch to Kafka, you can use Spark or Storm instead of cron. If you switch to Kinesis, you might choose AWS Lambda. So you're not writing yourself into a corner.
I've always wondered about this: doesn't the OS read buffer pretty much keep the whole thing in memory anyway, if you have memory to spare? Does it make that much of a difference?
In the beginning I also thought it would be about multiprocessing, and asynchronous execution. But actually it's just a programming style for linear, single thread execution.
Personal opinion: It's not needed if you don't want to code fancy. In some regards its even disappointing because the name promises to solve a generation old Python problem, which it actually doesn't even tackle.
For example, would it be a bad idea to put my email module on an async function as opposed to in a different application?