For example, I've been working on a side-project for about a month now, and I feel like half of that time has been spent going back and forth and back and forth between different code design decisions.
First I wanted to implement an event-driven architecture, so I read articles and watched talks and landed on an event-sourcing style system, where I'd persist a log of all events that occurred in the app. I also incorporated DDD concepts like repos and domain objects, and persisted all app state in SQlite.
Then I realized that I was making things way too complicated for the first iteration of my little project, and tried to re-write it in a very plain and straightforward style, e.g. just using an ORM instead of a repo pattern, no event bus, keeping more ephemeral state in-memory; just objects talking to other objects.
But even after I completely re-implemented everything, I _still_ feel like I'm making things way too complicated, and am getting worried that maybe I'm just incapable, for whatever reason, of writing straightforward, simple software. When approaching a problem, I feel like my brain skips about 10 steps of simpler options before making a decision.
I'm looking for any advice or material that would help be get unstuck in my software building process and learn how to write _simple_ software.
EDIT: Specific ask: does anyone have any go-to examples of well-written, simple software? Either an actual open source repo, or a walkthrough-style article.
(1) you are still young and learning
(2) you should broaden your toolset (patterns) and sounds like you are already doing this, good job!
(3) with time, you will become better because you have more options to select as the best option given the context
(4) all software seems to experience the curse of complexity, it's hard to keep it simple once you have to deal with the real world and all the edge cases that it presents
Sounds like you are doing the things you need to be doing and just need to keep at it.
---
edit, one strategy you may consider is to solve the same problem (program the same thing) with different languages, technologies, and patterns. After, try the same thing on something else. This will give you a breadth of contexts
another strategy is to work on the same project for a long time. This will uncover problems you only reach when a project becomes sufficiently mature. Often you will need to rewrite, refactor, and swap out technologies or patterns.
It blew my mind because, I was already applying it in my daily life without realizing it, but in a different method: I would write things down to help me clear up my thought process; eventually I would organize my notes as bullet points and whatever could be placed under a specific bullet point, it would end up as sub-category, well sub-bullet that is.
Eventually it helped me keep my mind organized, let alone my projects, and meet the deadlines with approximation 95% success; the rest percentage is for unpredictable circumstances that can happen to anyone of course.
So, my advice would be as follows:
* Start small, start simple (KISS principle).
* Have your project's logic written down; personally I prefer on traditional notepad.
* Monolith first; most of the times going full microservices in advance is a premature action.
* Prefer battle - tested libraries than writing your own.
* Ask for advice or feedback if you get stuck.
* Invest time reading others' code; that's how you become better at programming.
* Last but not least, apply the UNIX philosophy (if possible); "Write programs that do one thing and do it well."
I hope my feedback helped somehow ¯\_(ツ)_/¯ ...
That said, things often become complicated because you don't have the skills to keep them simple (for example, you draw the boundaries between your modules wrong, or you fail to abstract the right things, leading to tight coupling and information leaking). So it's pretty normal for your software to be a mess for the first decade or so.
You should just keep writing your project, and as you find as the developer certain things on it are harder than they should be / frustrating / take too long, that will indicate a problem. Then you can keep trying to refactor until you get it right. Writing bad software is how you learn to write good software.
You asked for an example and I just gave you a bunch of philosophy, so I'll give you at least one. It depends on your language and what you're doing but I use Go a lot, and I think Stripe is pretty good at keeping things straightforward https://github.com/stripe/stripe-go
My advice would be to try to optimize for what makes it possible for you to get things done the fastest. Ultimately that's the point of any abstraction, that and improving readability. Don't try and apply fancy patterns, just focus on solving the problems you're setting out to solve. If you find yourself working on a project long enough then you'll probably naturally find a need to start applying more complex architectures and abstractions, but it's almost never a good idea to start with those, even on a large project at a company that everyone "knows" will have to scale. Maybe especially in that scenario.