(I don't do this using sourcery though ... I have my own set of rules)
(I don't do this using sourcery though ... I have my own set of rules)
Basically, you have an api class for each Django app, and you use this class for all external interactions. The api class calls the service class, and the service class deals with the Django ORM. I added a view class, which is my DjangoRestFramework layer; so when a request comes in, it's caught by my view class, and passed onto the api class. I have DRF serializers for outgoing data, and pydantic schemas for incoming data. I also have a selector class for read-only views of my data.
It's a lot of typing, but I know exactly where everything is when something goes wrong, or I need to add a small adjustment somewhere, also it's easy for new devs to learn and use. One downside is that an api change require you to touch a dozen files.
I have had trouble using Django API Domains interfaces.py, so I left them out. The main point is, figure out the right balance of concise code and separation of concerns for your taste, and your stack. Good luck!
Similarly, you could collapse "selectors.py" into "services.py". I put read-only operations into "selectors.py" and write operations into "services.py", but you don't have to. I got that idea from this styleguide: https://github.com/HackSoftware/Django-Styleguide which is in the Appendix of the Django Domain API docs.
"For example, a high maturity package is not allowed to depend upon a low maturity package. Similarly, a core library package is not allowed to rely on a package that is specific to a particular product or a particular piece of bespoke development." I really like these.
Some of this metadata gives traceability information for requirements, tests etc.. while other metadata enables me to associate a maturity level with each file.
My build system understands this metadata and uses it to inform e.g. the minimum test coverage that it expects on a file-by-file basis.
The same metadata is used to ensure that all of the other components that a file references are at the same level of maturity or higher.
I also have metadata for each file (partly derived from location in the repository) that gives each file a number which defines it's position in a hierarchy of design elements.
The position in the hierarchy helps to indicate what the purpose of the file is. I use this to make a distinction between those core, foundational, stable design elements upon which other design elements may build, and those more peripheral, ephemeral and 'agile' design elements which can be quickly tailored to meet the needs of a client or partner.
This means that a (hopefully stable) core API component can be prevented from relying upon a (perhaps less stable) bespoke customer-specific component. It also means that there's more freedom in changing and adapting peripheral designs as you can have confidence that it's stability is not something that is going to be relied upon.
"It also means that there's more freedom in changing and adapting peripheral designs as you can have confidence that it's stability is not something that is going to be relied upon." That's a big advantage, indeed.
I also like the concept of storing this metadata next to the code in structured comments.