Applying DDD, CQRS & ES (Domain-driven Design, Command Query Responsibility Segregation & Event Sourcing) have by far been the most important decisions we've made in terms of reducing complexity in our app.
We started simple, with an ORM, Active Record & Transaction Script. As time went on, we realized the core domain had heavy amounts of Reactive behavior and Invariants (when this happens, send and email, when that happens, do this to something else). The Anemic Domain Model (https://martinfowler.com/bliki/AnemicDomainModel.html) caused an explosion of cyclomatic complexity in our core domain command handlers, causing slow turnaround time to meet business needs.
We use DDD to build a shared ubiquitous language (https://martinfowler.com/bliki/UbiquitousLanguage.html) with domain experts on our Miro board. With this language in place, we apply Event Modeling (https://eventmodeling.org/) to design workflows, complete with wireframed UI's for our team to implement. This allows us to find our bounded context boundaries early before writing a single line of code, since we can see where all the data is flowing to and why.
We use CQRS (https://martinfowler.com/bliki/CQRS.html) to separate our command processing aggregates from our queries, as well as build a dimensional model asynchronously for analytical reporting.
We use Event Sourcing (https://martinfowler.com/eaaDev/EventSourcing.html) (Redux state container pattern) sparingly in our core aggregates with the most complex behaviour, but do not persist JSON event streams. Instead, we map from a relational structure to events, fold the events into current state to make new decisions, then persist events + current state back to relational writes. We ensure that the columns we map from are always immutable.
This removes the common versioning problems that exist with having to choose stream boundaries up-front, and allows us to have an immediately consistent queryable SQL tables of our source of truth.