3

Say I have a business model called Vehicle. Vehicle has many fields but to keep it simple say it looks like:

public class Vehicle {
    String ownerName;
    String brand;
    FuelType fuelType;
    
}
public enum FuelType{
    GAS,ELECTRIC,PETROL
}

Now, this model has its own semantics in the domain. For example, this model is an entity meaning it gets persisted in a "something the GUI should not know about" (database). So there is a VehicleService that manages things like this. There are also things like VehicleExportService that exports a business.Vehicle(s), say to PDF files.

Now, the GUI (which is desktop) needs to show vehicles. If I use this model to show and let user edit vehicles, many parts of my GUI (ToolTipCreators, Controllers, Widgets) depend on this class (has import business.Vehicle) hence I consider this as "coupling" (please, give me a clarification if its not). Plus, during "store vehicle edits by user" phase or during "export" phase, parts of the application (mostly controllers) must depend on VehicleService and VehicleExportService. Changing things in domain, things in GUI must change too. Also, during GUI tests I will have to mock VehicleService. If one day I delete VehicleService, GUI tests will break.

So, in order to decouple and increase modularity, I introduce a gui.VehicleModel (MVC/MVP/Whatever). This allows me to add a save() or export() method. It is also an interface to let me test, without creating business.Vehicle objects during GUI testing or need to mock VehicleService.

Now, these 2 meet at gui.adapt.VehicleAdapter class that implements gui.VehicleModel. It takes in constructor a business.Vehicle, a VehicleService and a VehicleExport service and long story short, it just delegates the calls to business. For example, it looks like this:

public class VehicleAdapter implements VehicleModel {
    public VehicleAdapter(Vehicle origin, VehicleService service, VehicleExportService exportService) {
        ...
    }
    //getters and setters for all Vehicle fields
    @Override
    public void save() {
        service.save(origin);
    }

    @Override
    public void export() {
        exportService.export(origin);
    }
}

Now, the whole GUI works with VehicleModel. Changing business.Vehicle or say business splits VehicleService to VehicleSaveService and VehicleDeleteService will only require to change the adapter.

After all that are done, I think decoupling is achieved. I use this approach for other models (say VehicleOwner, or Brand or whatever) too. I have a gui.adapt package that is the only package accessing business package. However, what I find suspicious is the duplication between the 2 models. Between the MVC model (for GUI) and the business model. Vehicle has 30 fields (give or take). So, the interface has 30 getters and setters. The business model has 30 getters and setters and the MVC model has 30 getters and setters as well.

Am I overengineering? Should I use business models in the GUI? Do I have wrong impression of what "decoupling" means? Do I have wrong impression of what DRY stands for?

1
  • Models (classes) shouldn't have getters/setters only, as this would be considered procedural. Instead, they should have behaviour, they should do something, in the case of a domain model, that would be mostly the implementation of business rules.
    – Hans
    Commented Jun 22, 2021 at 9:54

1 Answer 1

6

However, what I find suspicious is the duplication between the 2 models. Between the MVC model (for GUI) and the business model. Vehicle has 30 fields (give or take). So, the interface has 30 getters and setters. The business model has 30 getters and setters and the MVC model has 30 getters and setters as well.

A class represents a single responsibility. Two responsibilities should be represented by two classes. This remains true regardless of whether these two classes are structurally similar or not.

The existence of a class is not related to how unique its structure is, but how unique its purpose (i.e. responsibility) is.

You're already touched on this: changes to the domain don't always need to be reflected in the GUI, and vice versa. Therefore, the domain vehicle and the GUI vehicle have different responsibilities, and therefore need to exist independently.

Do I have wrong impression of what "decoupling" means?

You're mostly spot on. Your proposed system ensures that the domain and GUI have their own lifecycles, where one can be altered without (necessarily) the other. I say necessarily, because there are always some possible changes that can't be ignored (e.g. if data disappears or changes type).

Do I have wrong impression of what DRY stands for?

Your understanding is mostly correct, but DRY should not be a leading principle for your architecture.

By that I mean that not repeating yourself is good when done where appropriate, but an overzealous application of DRY often leads to justifying bad practices such as tight coupling or creating an inflexible codebase (e.g. by not creating two structurally similar classes with different purposes).

In other words, because your two vehicle classes have a different responsibility, one is therefore not a repetition of the other, and therefore DRY does not apply.

Am I overengineering?

No. But be very careful about asking people's opinions about overengineering (or underengineering), as these are subjective labels.

As a consultant, I've been assigned to customers whose dev teams had drunk the bad practice koolaid and actively advocated against good practice. Tests were seen as a waste of time and a lack of self-confidence, SOLID was laughed at as busywork for bored nerds, build servers were considered an unnecessary complication "when we can copy/paste the DLLs ourselves", layer separation didn't exist because "it's all the same anyway". I'm quoting people here, including the tech leads and senior architects. It was an ingrained company culture.

If I talked about any good practice approaches such as decoupling, the general feedback from those dev teams was to label my suggestions as "overengineering". Similarly, I labeled their current practices as reckless underengineering.

The point I'm trying to make here is that you really need to understand the person who you're asking the "am I overengineering this?" question to, because their answer only makes sense to their own opinion.

8
  • I was kindly aware that my question (and the answers I would get) is opinion based. However, as an (almost) graduate student opinions can help me too. Because I can't be sure that I have analyzed all scenarios in my mind and not miss something. The reason I asked this question is the tests in my GUI (I can't live without testing my GUI, it will give me nightmares). I was thinking to go with the business models (overzealous DRY as u said).
    – George Z.
    Commented Feb 15, 2021 at 0:27
  • If so, I mock VehicleService and VehicleExportService for the presenter and yes, tests run. But then I thought, well, what If I create the VehicleXService in the future?I will have to come back to all presenter tests and fulfill the VehicleXService (constructor) dependency. So I ended up with "hey, that does not sound right. Perhaps I should make the whole GUI an autonomous module and just adapt everything under a package (or module). Then, "nothing" in GUI will have to change". But since it felt weird copy pasting properties It made me suspicious. Thank you for your time.
    – George Z.
    Commented Feb 15, 2021 at 0:27
  • 1
    "Wow, Flater ... I feel much better now." I thought that I was the only consultant left who had ... :-) Commented Feb 15, 2021 at 0:59
  • Why did they hire you in the first place then ? I reached this question to know if I was overengineering too, and to some extent I think there is a case to be made for it too. It's just that decoupling goes hand in hand with more repetition. So I don't think you can blindly say, there is too much coupling here, it's on a case by case basis no ?
    – Ced
    Commented Jul 14, 2022 at 8:26
  • @Ced: It is, by a wide margin, better to create a layer too much than it is to create a layer too little. Repetition is trivial. Having to separate existing code into separate layers and interfaces is pure hell. Secondly, avoiding necessary layers is a honey trap. People get lured in by the quick deliverables at the start of the project, and by the maintenance phase of the project they realize that they painted themselves into a corner they can't get out of without major rewrites, which will never be approved since the expectation of quick deliverables has been set.
    – Flater
    Commented Jul 14, 2022 at 11:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.