Today, I mentored a coworker on Separation Of Concerns commonly SOC. We are working on a bit of a pet project that he has some cobbled together code for. Instead of working directly with that we are starting from scratch and building out the features one at a time.
One concern we encountered is that each user has their own collection of widgets that they do something with. Some users may have the same widgets among their collections of widgets, but the widgets themselves belong to no single user.
We broke out a completely bounded context of User Settings. Though some information may cross the boundary into the Widget World, the only thing that shall pass is immutable data. In this way we have user settings that involve widgets and any other number of concerns such as app theme, gadgets, notification settings, etc.
Given that we are taking this on as a learning experience, we decided that we would (in the style of Clean Code) hold off on deciding persistence. What we do have is an object that loads UserSettings and one that saves it. Why the separate objects? Actually we have separate contracts, that's the part I do care about. The implementation can be the same for now. What the read/write separation is a out is control. It costs little to separate them now for the possible migration of the implementation to services later. It may be a bit crufty, but I can live with it in this case.
More central to the tier of persistence, is the layer of UseCase informed logic. In our case we have different concerns for user settings. One is centered on widgets, another on gadgets, another on application look and feel. There could be others. Each of these will be separated into their own bounded contexts of sorts. We are using the term Manager to describe this layer.
So we have a Manager for each concern - widgets, gadgets, app settings, notifications, etc. Any given Manager could talk to any data access context (Resources). Say a user adds a widget to his/her list of widgets that they want to have a look at. The Manager consumes the User Settings resource - loads settings, adds widget id to user.widgets, calls resource to with save method passing UserSettings object to method. All details of saving are abstracted from the business logic by the resource. The resource could be saving to a file, an RMDB, a document store, or some newfangled thing. We can change that without affecting BL.
If we have an app setting like 'theme' we can follow the same practice. Add theme id to the UserSettings object, make a ThemeManager or add methods to the UserAppSettingsManager and call the same load and save methods in the UserSettingsResource. We may have that same manager talk to the AppSettingsResource to load specific details about the theme. Likewise, the WidgetManager would load widget data from the WidgetResource after loading the list of widgets for the user from the UserSettings resource.
This type of pattern allows separation of concerns in two dimensions - by topic and by layer. It allows us to structure a program in services or direct use of objects. There is a slight deviation from true OOP in that things can get a bit procedural or perhaps functional in style. However, given proper management of concerns this may not be detrimental.