design – Is my understanding of SOLID principles correct regarding my concrete implementation?

Interfaces should be dictated by the consumer of the code. Client code if you will. That is, the part of the code that uses it. In this case – if I’m reading correctly – that’s the controller.

The question is then: What interface does the controller need? (Not what interface does the service provide).

Figure out what interface does it need, and create that interface. If that results in an interface per service, it would be because it is necessary.

Why do you want those interfaces? In this case, you want the service and its proxy to share the same interface, so you can initialize the controller with the proxy, and it will work just the same as if you initialized it with the actual service. Same goes for mocks, if you need them for testing.

Who initializes the controller? The composition root. Look it up.

Does the proxy have too much responsibility? Well, break it up. You got to initialize the proxy with an instance of the actual service, right? Well, you could give it another proxy. That way you could have proxies doing caching only, proxies doing validation only, and so on.

Oh, by the way, I haven’t said that each class should implement a single interface. If it makes sense that a single class implements multiple interfaces from those that the controller needs, great.

Interface segregation principle is not about making interfaces small just because. It is about providing exactly the interface needed. With no extra stuff. It makes perfect sense that a single class would provide multiple of these interfaces if it can be used in multiple ways. However, a method that takes an object of a given interface… Should have the type of that parameter by an interface that describe exactly what the method uses. No extra stuff. The smaller that interface is, the easier it would be have multiple implementations.

I believe, in fact, that your two quotes (the one on fine-grained interfaces, and the one on interface segregation principle) are saying the same thing.

Services having a lot of functionality does not go against interface segregation. It would only suggest they would need a lot of interfaces, each.

Instead, I would argue for services having a lot of functionality being a smell of broken single responsibility principle.

If your service classes are doing too much, I’d suggest to have your service class have the responsibility of bringing together smaller classes that have more specific and clear responsibilities, in such a way that they provide the intended service.

Have I been saying classes and interfaces? I probably should have been saying types and contracts. Subtle difference. Yet, if we generalize that way, then a type could be a lambda and the contract the signature. Sometimes when you have small classes with specific and clear responsibilities they end up with a single method, it is not disimilar.

What kind of tool would we use specify the logic flow of data without going into the details? Something like composition of generators. Like C# Linq. Something that takes cues from functional programming. Thus, if that is the problem you are running into, I’d suggest to borrow some ideas from there. You are doing something that looks a lot like data-in/data-out anyway.

Edit: Oh, by the way, you co can do your proxies like that too, instead of chaining by the interface.

Recommended: Find a recent recording of the talk “Functional Principles for Object Oriented Development” by Jessica Kerr.