We're developing a big application with a vast GUI and complex business layer. Without going into too much detail, the application is currently implemented in a rich native client, but functionality is roughly seperated into core business logic related parts and the (also complex) GUI which is based on an MVC paradigm. (Here i must say that the "model" of MVC is tightly linked to the GUI, given that it comes from Windows GUI library)

Plans are to migrate this app to the web, where proper separation will become even more important.

The application features Undo/Redo, based on a classic Command pattern that encapsulates business-related logic. However this command pattern currently "lives" in the GUI part, and commands themselves have great dependencies on the GUI subsystem (notable for UI refresh related functions). Our idea is to move it deeper to the business core layers, which will become part of the backend of a potential web-based solution.

But this raises a fundamental question. Is Undo/Redo functionality supposed to live in the business layer at all? Where would you put it and how would you justify that design choice?

  • Business layer doesn't mean a hard separation between client-server. Internally a business layer can consist of many more layers. Some which might even run on the clients themselves. Important is that the logic is separated from the interface. So the interface calls the business logic. But part of your business layer, like undo, could just be a class which runs on the client. It can even be implemented in multiple languages like native Swift code and Javascript for the web version. Or you could share the code and run the functionality everywhere in Javascript. It depends... – Luc Franken Oct 6 '16 at 14:23
  • you answered your own question. You're changing user interfaces. – radarbob Oct 6 '16 at 15:11

Surely this totally depends on your business.

  • Example 1: document editor with save functionality

The editor itself might give you undo/redo as you edit, but those commands arent persisted via save. its not part of the product to supply a history of changes to the document. -> GUI logic

  • Example 2: audit trail for ordering process

each change to the order is logged and can be rolled back if required. A business requirement for the product is that you see all of individual changes and can roll the order back to any point in time. -> Business logic

  • 1
    Well, it's somewhere in-between. The app is far more complex than a document editor. It needs to create complex hierarchies of business objects, going through various UIs. It's state is persisted to a database on the user's request. But the history of actions is not part of that state. So Undo/Redo could be limited to the UI. But an advantage of being part of the business layer would be that the complex object creations could be reused by different presentation layers. (web vs native app) – Scrontch Oct 6 '16 at 14:56
  • or it might make it onerous to implement, or give a bad user experience due to all the extra api calls. you should determine what the actual business requirements are and separate out any extras into another layer, which need not be the UI layer, maybe some intermediate service – Ewan Oct 6 '16 at 15:28

The fact that you struggling with decision where you need to move your object/class/package - pointing to the fact that this object/class/package already violate separation of concerns or even better violate Single responsibility principle.

The application features Undo/Redo, based on a classic Command pattern that encapsulates business-related logic.

So your command object already have encapsulated some business logic

However this command pattern currently "lives" in the GUI part, and commands themselves have great dependencies on the GUI subsystem.

Commands depend on GUI, so then leave commands on UI side but move business-related logic to the business layer.

Currently you commands violate your presentation and business logic layer separation.

So you need split them to two parts (presentation and business).

  • Currently, the commands know the business logic, as they implement their execution in terms of business functionality. The question is whether the command objects themselves should defined in the business- or presentation layer. – Scrontch Oct 6 '16 at 14:30
  • @Scrontch, separation of concerns means separate your application to the layers with separated responsibility. So you need separate your command to two layer. Any decision you take move it to presentation layer or move it to the business layer as it is without splitting it up - will violate separation of concerns. – Fabio Oct 6 '16 at 14:48

I hope I got you right:

I'd say if your application is doing CPU intensive work, I would simply call an undo function in the core. I'd even do this with cross-thread-communication since undoing something might as well get very complex.

This gives you even future proofness as a bonus because if your'e willing to implement more functions / a messier GUI your Window thread isn't causing much more overhead and your code will simply bee nicer to look at.


Building on @Ewan's answer regarding audit:

Undo/redo is supposed to work like a stack mechanism, and generally only allows undo at the top of the history (and redo as long as no other changes). This undo history stack is, for all practical purposes, tied to the session that a user is in.

Maintaining a long-term undo/redo history stack in the business layer could problematic, in a number of ways, especially in the context of a multi-user collaboration where the illusion of a single history stack kind of evaporates.

Instead of providing (a problematic) undo/redo as a history stack in the business layer, you might keep a change log in the business layer, and, provide the user with features that help them build a reversing command from any given change in the log whether it is the last change or not.

(You might also capture in the change log whether a command was intended for reversing a former command to help make the change log more digestible later.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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