How to avoid god controller classes and keep single responsibility principle?
https://softwareengineering.stackexchange.com/questions/291134
Frage
The task is to make a migrator from Old DB to New DB using OOP Single Responsibility Principle.
My problem is how can I make this without making the controller a God Class or breaking the single responsibility.
I've thought of two methods of implementing this
Iterate through each property row and cascading to Subdivisions and Owners in the controller and call a Row Migrator for each entity type
- Been told that is giving too much responsibility to the controller and would make it a God Class
Add the iterations inside each Entity (E.g. Property iterates Subdivisions, Subdivisions iterate Owners)
- Been told that it is breaking the single responsibility principle
Right now it feels that anything I think of it either over-complicates or breaks rules. Maybe I've understood the principles wrong or there is a third way.
Update 1
Normally I would do this with a script but the requirements are to use an API as the new DB is on a different platform, and the addAddress() and addOwner() logic will be in an API Component.
Lösung
OOP is not always the best choice for ETL tasks, thus I would not stick to "pure" OOP here. And as long as your model is as simple as shown in your example with just three tables to migrate, I would avoid to overdesign this - having simply one Controller which orchestrates the migration of just three tables, and which uses different "Row Migrator" classes, as you wrote, does not look to me as if the Controller gets "too many responsibilities".
Nevertheless there are some options to distribute the responsibilities. For an ETL program, I would suggest to split the responsibilities between the "E(xtract)", "T(ransform)" and "L(oad)" parts of your migration - the "Extractor" class is the only one which connects to the old DB and provides the data to migrate, the "Load" class the only one which connects to the new database / uses the API, and the "Transform" class which does the transformation work, but does not know anything about the source or the destination of your data. Moreover, when your model is much bigger in reality as shown in your example, you might consider to split the "Transform" class into smaller "sub-transformers".
If your second option makes sense IMHO depends on if you are going to use the entity classes exclusively in the migration context, or if you want to keep them reusable and maintainable for different contexts. For the latter, I would not add any business logic there which is only useful for this very special use case "migration".
Andere Tipps
You don't need a "controller", you need a "service."
A Controller controls the flow of the application based on user input and other events that occur in lower layers. A Service just naively goes about its business doing what it was told.
The migration service object would have the intelligence for pulling data from the old schema and persisting it to the new schema via a web service if necessary, or an entirely different API. This service should have the minimum amount of dependencies to get the job done:
- Domain model classes for each database
- Data access classes for each database
- Methods or other classes that map one domain model to another
The PropertyMigrationService
has the following responsibilities
- Knowing where to pull data from
- Knowing where to save data to
- Having access to the mapping objects to map one domain model to another
It would need:
OldDataAccess
- A class for CRUD operations on the old DBProperty
- The domain model for the "property" in the old DBSubdivision
- Domain model for subdivisions in the old DBOwner
- Domain model for owners in the old DB
NewDataAccess
- A class for CRUD operations on the new DB/web serviceAddress
- Domain model for properties in the new DBOwner
- Domain model for owners in the new DB
PropertyToAddressMapper
- MappingProperty
objects in the old DB toAddress
objects for the new DBSubdivisionToAddressMapper
- MappingSubdivision
objects in the old DB toAddress
objects for the new DBOwnerToOwnerMapper
- Map owner objects from one DB to another
You'll end up with this object hierarchy:
PropertyMigrationService
OldDataAccess
NewDataAccess
PropertyToAddressMapper
SubdivisionToAddressMapper
OwnerToOwnerMapper
The migration service could have a single public method called void migrate()
that performs the migration.
Having dealt with legacy schemas, you never know what manner of madness and chaos you'll encounter. Architecting the solution in this manner, in conjunction with having your data access classes implement an interface, and have the PropertyMigrationService
use those objects through their interface, you can make this whole migration process unit-testable.
Now you can throw a bunch of tests at this for known error conditions and really make this thing bullet proof (well, bullet resistant).
After this is wrapped in a service object, you have some flexibility for when and where this gets invoked:
- From a "controller" in a web application
- From a script kicked off as a cron job in Linux or scheduled task in Windows
The migration process becomes both proactive (user clicks a button on the web page and does the migration) or passive (a schedule task executes at midnight and does these records in batches of, say, 1,000).