Frage

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?

War es hilfreich?

Lösung

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
scroll top