So one thing we've done is write all of our applications the exact same way with well defined terms (on a wiki) and a commitment to no more than 4 layers.
* Initiators (things that receive, decode, and validate input)
* Controllers (Business logic containers. One function refers to one business action)
* Services (Used by controllers to effectuate commands. Services absolutely cannot call other services)
* Cross Cutting concerns (Common model objects, logging, top level error handling, etc)
This allows even a new person to pick up a project and orient themselves immediately.
First: we found that it creates a spider web of dependencies if we din't have this rule. Instead, if you return control to the controller after performing a discrete action, it makes sure that business logic stays out of services and keeps service functions short and directed.
Second: it makes it easy to keep the entire design in your head.
Third: It promotes composition. This leads to easier testability with mocks rather than having to resort to full blown integration tests for even the smallest things. (We still do integration tests, but mock tests can be churned out in volume and are less fragile).
Yes. So something like a "DatabaseService" might have a database connection, which is code we don't own. But the code we do own stops at that layer. This prevents a spider web of dependencies opening up.
* Initiators (things that receive, decode, and validate input)
* Controllers (Business logic containers. One function refers to one business action)
* Services (Used by controllers to effectuate commands. Services absolutely cannot call other services)
* Cross Cutting concerns (Common model objects, logging, top level error handling, etc)
This allows even a new person to pick up a project and orient themselves immediately.