1

I've been trying for some time to use the coding/design principles shown in the seemingly rather insightful, but for a time quite hard to decipher, post here:

How should a model be structured in MVC?

by tereško (it is/was hard to decipher because it seems to be heavily oriented toward a Web/Internet programming audience, and that is not a field with which I had much familiarity - most of my experience centers on desktop and mobile devices i.e. "the whole program runs on one device" paradigm). And one thing that has now come to bug me is the relationship between the Domain Objects, as he describes them, and the Services, also as he describes. I've later found that this appears to be taking ideas from a broader area called Domain-Driven Design (DDD), and while there seem to be a fair bit of resources on this topic, a lot of it is expensive books I cannot afford to pay for, so I have to try to wrack myself with more limited internet-based articles to get a feel for what all and everything that is being talked about. I'm not necessarily interested in perusing a full and rigorous "DDD" setup, but rather just the broad strokes of the pattern that tereško outlines, in a consistent way particularly in light of other "good code" principles like SOLID.

And that's the one that in particular I seem to have run into a bit of difficulty with in the following case. Suppose you have an app that functions as a diary (I've made such, but I'm actually thinking of something else here). As I get it from the above, an object called "Diary" might live in the "Domain Objects" portion of the program and do and represent just what you think it would. Likewise, in the UI layer, where we may have the "View" and "Controller" arms of MVC, or maybe "View" and "ViewModel" if we're using MVVM or whatever else is appropriate to the MV* flavor we want. The trick though is particularly the whole "interface" business suggested by SOLID - in fact, 4 of the 5 letters, all the ones after the "S"! This part seems clear enough (using a Java-style code):

Consider the "New Diary" command on the user interface. This button will send an event to an MVC Controller, which then must use a Model Service interface, presumably like this to satisfy ISP of SOLID (don't have available for calling more than you need):

public interface IDiaryCreatorService {
    void createDiary(String name);
}

Model Layer contains an implementation. This is where the trick is. Presumably, the implementor is going to call a Factory of some kind that will produce new Diary domain objects, and will be dependency-injected into the Controller:

// in Model
public class DiaryCreatorService implements IDiaryCreatorService {
     @Inject private IDiaryFactory mDiaryFactory;
     @Inject private IDiaryRetainer mDiaryRetainer; // implemented in Data layer

     public void createDiary(String name) {
          Diary diary = mDiaryFactory.create(name); // concrete Diary is exposed to service!
          mDiaryRetainer.retain(diary);
     }
}

Note the commented bit. Does this seemingly very reasonable design - note also that while I imagine that IDiaryRetainer is to be implemented as one head of a DiaryRepository down in the Data Layer, I have made DiaryCreatorService not depend on a full IDiaryRepository interface, again, because of the "I" in SOLID as applied to the caller, as this caller dose not need to know of, say, any Retrieve method it may have - still end up as "bad design" because in some sense "DiaryCreatorService" depends on, since it "sees", the concrete class Diary which is the Domain Object? Yet it seems there's no other way to make this work, because that has to get - in the "job description" of a Service as a "higher-level Domain Object", from the factory into the database or whatever backing collection the rest of the program draws on to do further operations!

Now I can figure some ways around this. One would be to simply @Inject a different interface that replaces the Factory and which also can receive a suitable receptacle. This object actually directly calls the construction on the Diary, as in a Factory, and then pushes it to the receptacle, but that also seems now kind of ugly because it feels kind of SRP-problematic.

The other way is to have the Factory interface not return a concrete class, but instead only return some kind of opaque ADiaryStub or something that Data Layer then receives through IDiaryRetainer and then downcasts as need be, but that again, seems kinda funny because of the downcast which doesn't seem like it should be necessary here.

What am I missing? How strict should one interpret or not the "knowledge hiding" aspects of the OO principles for these kind of situations?

The_Sympathizer
  • 1,191
  • 9
  • 17
  • Where did you get that a domain service using a domain object is "bad design" from? I would say the opposite, that's how it's supposed to be. A domain service will either create or retrieve a domain object and then call operations on it, like order.Accept, delivery.Dispatch(), etc. – Francesc Castells Apr 09 '22 at 20:59
  • @Francesc Castells: I get this because isn't that what it means to "not depend on a concretion"? If the service object can see the concrete instance of Diary with all methods potentially available for use, then that means it "depends" on it, no? I guess that's the trick - what kinds of "dependencies" are being talked about in those OO principles? – The_Sympathizer Apr 09 '22 at 21:20
  • Right. Well, that makes sense for things that can be abstracted away and the actual implementation is irrelevant from the business logic point of view. For example, to retrieve and store Diaries, you define an abstraction and at runtime, you can use any implementation that complies with that abstraction. But there is only one Diary, so abstracting it wouldn't give you anything. Domain Services can depend on domain objects, because their purpose is to operate them. – Francesc Castells Apr 09 '22 at 21:46
  • @Francesc Castells: Yes, however services are not necessarily going to do _every_ operation unless the domain object is very simple, because of the Single Responsibility Principle. But then the Interface Segregation Principle suggests "they shouldn't know about more than they actually need to". So why expose them to all the levers? – The_Sympathizer Apr 09 '22 at 23:43
  • You say: _"`DiaryCreatorService` depends on, since it 'sees', the concrete class `Diary`, which is the Domain Object"_. No, `DiaryCreatorService` depends on, e.g. "sees", `mDiaryFactory` and `mDiaryRetainer` only. It doesn't depend on, e.g. "see", the concrete class `Diary`. – PajuranCodes Apr 10 '22 at 08:59
  • You are afraid that _"// concrete Diary is exposed to service!"_. I don't see a problem. You can create a `Diary` object using the keyword `new` in the service, or in the factory. One of these will then be tight coupled to `Diary`. You need to somehow create a domain object in the service, in order to be stored in the database. – PajuranCodes Apr 10 '22 at 09:09
  • In my opinion, the design you've presented is a/the good one, as long as the service have only the responsibility of **creating a diary**. This diary creation implies, of course, multiple steps: creation of a `Diary` domain object, data validation - probably taking place in the domain object itself, persisting the properties of the `Diary` instance - in a database, for example, error handling, returning the result, etc. – PajuranCodes Apr 10 '22 at 09:11
  • _P.S_: You've just removed your answer... According to it, the factory would have wrongly had not one, but two responsibilities, e.g. reasons to change: to create a `Diary` object AND to pass it further to a receiver. – PajuranCodes Apr 10 '22 at 09:16
  • @dakis : Thanks. So when is the "give up point" to accept the tight coupling? Also, why does that not count as 2 responsibilities when in the service object, then? – The_Sympathizer Apr 10 '22 at 09:17
  • You are welcome. To 2: Beside creating a `Diary` object, passing `Diary` to a receiver is an **additional** factory responsibility. Whereas all of these multiple steps in the service are part of the **sole** responsibility of the service: creating the diary. – PajuranCodes Apr 10 '22 at 10:00
  • @dakis : Thanks, I suppose that make sense now, because the service has a broader "scope" of its responsibility given that is fundamentally a nexus between the domain, UI, and data regions of the program. Of course it still must only handle the "create" command, but its "create" command can be more involved. – The_Sympathizer Apr 10 '22 at 10:14
  • @dakis - right, I mean that the UI Controller _calls_ the interface, of course, not that the service object is sending stuff back the other way e.g. through some added dependency, or somehow is aware there is a UI controller on the other side of that interface. That is, a one-way "bridge". – The_Sympathizer Apr 10 '22 at 10:41
  • 1
    To 1): In [this video](https://www.youtube.com/watch?v=qIe6D81kr2g), James speaks, in the context of [DIP](https://en.wikipedia.org/wiki/Dependency_inversion_principle), about _composition root_ and _object graph_... If the creation of an object that you need in your application can not be handled in the _composition root_, then it must take place inside the code of your application (e.g. in the part named _"Dev Code"_ by James). It's up to you to decide where. If you need to create a `Diary` domain object, then a `IDiaryCreatorService` is the perfect place to do it. Or in a factory. – PajuranCodes Apr 10 '22 at 10:58

1 Answers1

0

What am I missing?

Possibly a couple things.

In Java, generics give you some extra flexibility for preserving decoupling and type safety. So you can write something like:

public class DiaryCreatorService<D extends IDiary> implements IDiaryCreatorService {
     private IDiaryFactory<D> mDiaryFactory;
     private IDiaryRetainer<D> mDiaryRetainer;

     public void createDiary(String name) {
          D diary = mDiaryFactory.create(name); // Only the interface is exposed!
          mDiaryRetainer.retain(diary);
     }
}

Here, the DiaryCreatorService doesn't care what kind of Diary you get, so long as both the factory and the retainer use using the same kind of diary.

(You may need to be careful with your dependency injection framework - type erasure may permit the framework to inject collaborators that aren't actually compatible.)

D here is just an identifier, so you can alternative spellings if you think it improves the design

public class DiaryCreatorService<Diary extends IDiary> implements IDiaryCreatorService {
     public void createDiary(String name) {
          Diary diary = mDiaryFactory.create(name); 
          ...
     }
...
}

What this means is that you can, if you like, define an interface for your domain model, and then any controller that consumes the interface can communicate with any implementation of the domain model.


In practice, I think you'll find that it is more common to couple controllers to "the" implementation of the domain model, rather than trying to decouple them. Take a look at onion/hexagonal/clean architecture, where coupling to implementations is expected when those couplings point toward the domain model.

My experience with trying to create a contract for the domain model (to facilitate changing the underlying implementation) is that legibility suffers quite a bit; therefore, make sure you really are going to get the return on investment before you start the work -- a bunch of fancy ceremony that gets used for only a single implementation is likely to be a bad trade.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • Thanks. So then I guess the SOLID rules on interfaces are not to be taken as dogmas saying to always build interfaces, but rather more that they are rules that kick in _once interfaces exist_, right? Instead, boundary decisions (where you would have a layer of interface "padding" in between) should be based in the architectural plan. So in this setup, the boundaries are UI : Model and Model : Data but not Services : Domain (a potential boundary _inside_ Model) as the latter can be _expected_ to be tight coupled. – The_Sympathizer Apr 10 '22 at 18:23
  • Of course the rules will still apply in other ways, e.g. all domain objects should follow S and O for example (note though O is more of an ideal too, than something you can 100% achieve, because it inherently has a "future-proofing" element, but "if you know you'll extend it, design for it"). However, the "depend on abstractions" stipulation should really be confined to either situations where you know you are going to have multiple implementations, or else to natural system boundaries (But once abstraction layers appear, then the last 3 rules, i.e. L-I-D, swing in to govern their building). – The_Sympathizer Apr 10 '22 at 18:24
  • What I was after in the question was really then whether Services : Domain is a natural boundary of that sort or not, and I guess this is really saying "no, it's not". And indeed most MVC and DDD style codes I can find seem to show that the services do use DomainObjects directly. – The_Sympathizer Apr 10 '22 at 18:24
  • @The_Sympathizer, yes, services use domain objects directly. – jaco0646 Apr 10 '22 at 22:48