For essentially all my projects, I end up having most of my business logic at the trunk of the project source. The leaf nodes are the most general purpose modules (e.g. database clients, utility functions), the branches are still relatively generic but closer to the business domain. The trunk (e.g. entry point of my program) changes very frequently and is directly related to the business domain. The branches change rarely and the leaves almost never change. The leaves are usually third-party modules. For the leaves, I will typically swap them out if they cease to meet my requirements. I almost never refactor those. For the branches, I sometimes have to refactor 1 or 2 of them at most. I can't recall ever doing a refactoring which touched on many branches at the same time.
Most of my refactoring only affect the trunk of the code. I try to have all the business logic for a program or service represented in a single file. If the file gets too big, I create new modules 'branches' and move the most generic logic to those branches. My top level file tells the full story of the program/service. Anyone can open that file and, based on the module names and method names that are being called, they can figure out what happens and when. All the events, endpoints, logging, access control and other externally observable behavior is wired together in that file.
If the file has been abstracted to the maximum amount possible (with all generic functionality moved to branch modules) and it's still too big, it may be time to switch to a micro-service architecture. Break up the trunk into 2 parts and we basically end up with 2 apps/services. This rarely happens though. With the right level of abstraction, you can fit a massive amount of user functionality in a single main file. By the time the main file's code becomes overwhelming, the application's UX becomes overwhelming for the end user too... Time to split up into multiple apps.
Think of how complicated UX would be if a video-editing software tried to support image editing as well (e.g. to make fancy title screens or UI overlays). It would become too much for the user. Just make two different applications; one for video editing, one of the image editing. You can integrate them in a seamless way, but they should be different apps.
This long comment doesn't contradict my point because "The trunk ... changes very frequently". Requirements change, and thus, code changes. There is nothing you can do about it. It's cool when everything is modularized enough that you never need to change some parts of the code. But it doesn't mean those parts of the code are better than code that changes often.
Now, if you think a little more about this, there is a huge risk that some of your leaf code should be in a library. For example, SQL builders, ORMs, UI kits, markdown formatters, etc. When it's a publicly available package used by many devs around the world, the code in it often changes. The reason why your leaf code doesn't change is that you don't have a proper investment return for small changes because you are a single user. Code in libraries handles more useful use cases, it handles more edge cases, it provides better API, and it contains fewer bugs than code in leaf nodes. And all that's while code in libraries is constantly changed. And code in leaf nodes... I would say that the proper description for it is "used, but dead".
Most of my refactoring only affect the trunk of the code. I try to have all the business logic for a program or service represented in a single file. If the file gets too big, I create new modules 'branches' and move the most generic logic to those branches. My top level file tells the full story of the program/service. Anyone can open that file and, based on the module names and method names that are being called, they can figure out what happens and when. All the events, endpoints, logging, access control and other externally observable behavior is wired together in that file.
If the file has been abstracted to the maximum amount possible (with all generic functionality moved to branch modules) and it's still too big, it may be time to switch to a micro-service architecture. Break up the trunk into 2 parts and we basically end up with 2 apps/services. This rarely happens though. With the right level of abstraction, you can fit a massive amount of user functionality in a single main file. By the time the main file's code becomes overwhelming, the application's UX becomes overwhelming for the end user too... Time to split up into multiple apps.
Think of how complicated UX would be if a video-editing software tried to support image editing as well (e.g. to make fancy title screens or UI overlays). It would become too much for the user. Just make two different applications; one for video editing, one of the image editing. You can integrate them in a seamless way, but they should be different apps.