Question Details

No question body available.

Tags

architecture refactoring architectural-patterns mvvm clean-architecture

Answers (2)

April 15, 2025 Score: 12 Rep: 120,068 Quality: Expert Completeness: 70%

I see you've identified the following problems:

  1. Repository layer sends raw data from the network api to the ViewModel forcing VM to do transformation and formatting making VM hard to test

  2. A "God Repository" that is referenced in a large number of places throughout the codebase

  3. Data transformation logic varies across different consumers (ViewModels), so there’s no single, consistent transformation

You've listed some solutions that sound like they'd help. But let me propose something:

Create behavior code that doesn't know how to find anything, doesn't transform the format of anything, and doesn't decide where to put anything. It does one thing, on the one format it expects, that gets handed to it, and puts the result wherever its told to put it. seriously

This is not what a Clean Architecture use case does. A CA use case knows where to find everything. Knows where to put everything. Knows how to transform everything. And that's fine. A CA use case can use the above behavior code to do its work.

What this does is separate behavior from knowledge of the world. That way when the world changes the behavior is minimally impacted. It also makes the behavior code easy to read (since its assumptions are simple) and easy to test (since you can drop it into any world you like; Even a testing one). This follows the teachings of functional core, imperative shell.

It also follows a less grandiose teaching. Humble Object simply says if an objects logic is hard to test then move that logic somewhere else easy to test.

Doing that will push back on all three of your problems.

  1. When your behavior code insists on a single format you're forced to do the transformation first, outside of it. This avoids crazy conditionals or weird parsing in the behavior code.

  2. This pushes knowledge of all the repositories away from the behavior code

  3. Since all behavior code insists on its own format, transformation logic is pushed out.

But there is more work to do. What you call a "God Repository" reminds me of a service locator. The cure for that is Dependency Injection. Old schoolers, like me, see DI as just a fancy new term for reference passing. I've talked about this stuff before.

You talked about work being done in little helper methods. Consider maintaining the separation they were providing but be willing to make these into full fledged objects (preferably immutable ones). Give your behavior code some respect.


Let me react to your comment:

Let’s say we have a large app where user data is fetched from a remote server. We have a Repository that is used in most ViewModel's. It connects directly to the network layer and simply returns raw data (a UserDto object from an API). Now imagine one ViewModel wants to show the user's full name in uppercase. It fetches the raw data from the repository and then formats the name manually — inside the ViewModel. Another ViewModel does something different, like displaying just the first name with a greeting. Again, it formats that directly inside the ViewModel. - developKinberg

Uncle Bob has a very different idea of what a ViewModel is than the MVVM people do. Since you said your code base is a mix of both CA and MVVM I can't be sure what you're dealing with.

But, in either case don't you have views? Or presenters? Or whatever you want to call them? Somewhere something has to know how you want that name displayed. I see no reason to let that detail leak into your data model. Formatting the name "manually" seems to be the thing to do. But code that knows how to format it and code that knows how to get it don't have to live together. Separate these concerns. Even if you make the CA use case responsible for the transformation doesn’t mean it can’t make some behavior objects do it.

Call them whatever you like. Just use names that make their jobs clear or they'll fill up with clutter.

April 15, 2025 Score: 4 Rep: 31,487 Quality: Medium Completeness: 30%

How would you approach refactoring a God Repository that’s tightly coupled across many parts of a legacy codebase?

One approach that allows you to transition away from this in incremental steps is to start defining interfaces for the 'God class' for each client class. This can be combined with DI or not. I'm not sure what language you are using but I'll assume Java or C#.

The basic idea is that you can create an empty interface in each dependent class and, implement the interface in the God class, and then cast your references to the God class to this client specific interface. This should result in compilation errors because your interface is empty. You then add the methods you are using from the God class to the interface. (If there are variable references, create methods to wrap them.) Once all the compilation errors are gone, your interface now represents all the dependencies from that client to the God class.

As you repeat this process across more client classes, you should start to see patterns. That is, you will likely have groupings of interfaces with a lot of the same methods. You may also find that there are distinct groups with minimal to no overlap. That, is you might find that there are a number of interfaces which have methods A, B, and C and other clients' interfaces which have methods D, E, and F. That's an indication that you can split the God class along those lines and consolidate the client interfaces into a coherent one. You can repeat this process recursively to further delineate the 'fault lines' on the God class. Not all of the clients will use every single one of the methods on these consolidated interfaces and some clients might depend on more than one. Don't think of this process as prescriptive. The idea is to help you tease out meaningful interfaces that make sense i.e. ones that you can give a reasonable name to.