13

Sorry if the title sounds too broad, I'll try to clarify. I am trying to understand the structure of applications that have a GUI, but any action can also be run from a CLI (either integrated in the GUI, like AutoCAD (reference picture), or simply by launching the entire application in CLI, circumventing the GUI altogether). Having worked with several such programs, I noticed that any manipulation of GUI elements automatically generates a command that shows up in the CLI, which implies that GUI inputs (for example textboxes) are not binded to some underlying data, but instead write values through arguments of CLI commands to modify that underlying data. I assume that reading data works in the same way (through data retrieval commands).

This seems like a very different approach from the modern binding-based UI approach (like typically done with WPF) that seems to be very popular nowadays. It seems that GUI being a runner for CLI commands would make it more difficult to build the GUI, but having an integrated CLI (like in that AutoCAD example) seems like a great convenience for the user. But I could be wrong, maybe these two approaches are not at odds.

My question is, can anyone explain how such applications are structured, and what are the best practices on designing them? Are my assumptions in the ballpark? I tried researching this, but I probably don't know what this kind of design called, so I didn't find much. Any links, resources or just a clarification of what I need to search for would be greatly appreciated.

7 Answers7

17

I noticed that any manipulation of GUI elements automatically generates a command that shows up in the CLI, which implies that GUI inputs (for example textboxes) are not binded to some underlying data, but instead write values through arguments of CLI commands to modify that underlying data

That's one way to do it, but not the only way. Let's use a simple example of approving an order request.

The CLI version could be myapp approve 12345. The UI version could be either a textbox (to input the order ID) and an approve button, or that approve button could be placed on the order details page (so the order ID is inherently known by the UI).

Somewhere in the code, you will find a method along the lines of public void ApproveOrder(int orderId). We won't explore its implementation for the sake of brevity.

Now, there are two ways these components all interact:

enter image description here

There is no clear winner between these two. The main difference here is whether the UI is intended to be a wrapper around the CLI (i.e. the CLI tools are always needed), or a replacement of the CLI (i.e. the CLI tools don't offer anything that's always needed).

That's contextual, and often an artefact of subjective design decisions made during the development process.

git is a good example here as one that favors the daisy chain. The git CLI is always required, because it is generally expected to integrate into externals tooling (IDEs). Because of this, the CLI already provides a full user interface, and thus git GUIs tend to just wrap themselves around that existing interface instead of trying to re-invent it.

However, a daisy chain would be disruptive if the underlying code is expected to dynamically generate images. Not that you can't e.g. pass a URL to the generated image via a CLI, but you very quickly start painting yourself in a corner with this, whereas option 2 removes the "must work via CLI" constraint that option 1 inherently entails.

Flater
  • 58,824
6

I would argue that this is not what is commonly called CLI, but a Console in an already running GUI that provides CLI.

Console is typically openable from within the GUI and allows to issue commands to the underlying engine. This is often used in games, where there is a game logic engine running under the hood. Games would often have this separation between logic engine and GUI since it allows a number of neat features:

  • game logic engine can run in a deterministic manner, where exact same inputs lead to exact same results
  • replays become easy - just record player input and repeat it anew to get the replay
  • multiplayer becomes easy - exchange player inputs and get the same result for every player
  • in-game scripting - modders can make the levels interactive and unique with ease
  • in-game console - players and testers can access all sorts of wild things on request
  • testing - running the game with a specially crafted script to reproduce bugs or generate data for analytics

Now you might already have some clue of how it might be structured - have a separation between logic engine that runs by itself and a bunch of control inputs coming from GUI, replay, script, multiplayer peers, console.

enter image description here


Using daisy-chain could be double work (that could also be suboptimal in performance-critical scenarios). It makes little sense to assemble textual command script in GUI subsystem just to pass it to the Console subsystem to be parsed and converted back to method call in the logic engine.

enter image description here

Kromster
  • 606
  • 1
  • 8
  • 18
4

Proper separation of concerns takes care of it all. Rather than handling all the stuff that that button press needs to accomplish in the event handler for the button, have the event handler just do enough to pass on the actual command to the same method that's triggered when entering a CLI or console command to do that operation.

So e.g. you made an approveOrder(orderId, authorisationToken, timestamp) method to handle approving orders. From your GUI's event handler, you call this method after calculating a timestamp and retrieving the other parameters. From the console you do the same, from a command line interface to the system you do the same too. Only potential difference would be the source of the data passed to the method.

jwenting
  • 10,099
2

If you have a "model-view-controller" internal structure, and especially if you use the "command pattern" internally, then the command line becomes just another form of View.

This can arise fairly naturally if you have had to build complicated tests for the application logic. In order to get it right, it will have been necessary to separate it properly from the GUI and run lots of unit tests, which necessarily run headless.

pjc50
  • 15,223
2

Applications like this use in general some variant of the command pattern as core way to operate on the model. The GUI operations generate internal commands, and CLI commands are just a way to generate the same commands. It is then easy to systematically display all commands performed in textual way.

In the case of Autocad, there's also a programmatic way to operate on the model, via AutoLisp. Although it has a different syntax, it generates in the end the same primitive commands.

Some context also: Autocad was there before Windows was lauched. At that time, GUI operations were not a native OS feature but had to be implemented in application. It was not uncommon in that time to start project with a quick text interface to manipulate the model and get some solid foundation before going to develop the GUI, which required a lot more work (every mouse producer had its own drivers and API, and in the case of autocad there where all these tablet producers which used absolute positions instead of relative ones, so different worlds of devices that are nowadays gently hidden behind standardized API)

Christophe
  • 81,699
1

Having the GUI run CLI commands is a possibility, but it's not necessarily the best or most common approach. While you may not have a choice if you need to wrap an already existing program, my impression is that when the CLI and GUI are developed together by the same team, it's more common for both to call some kind of underlying API. For example, a Python program myapp may have myapp.cli and myapp.gui modules that implement the user interfaces, with other modules implementing the core logic.

In the simplest case, that API can be internal to your program. Then you can change it without having to worry about backwards compatibility with code that you don't own, but it's harder for other people to write additional UIs.

One level of separation up, you can expose the core logic of your program as a library with a public API — for example, a Python package or a dynamically linked C library.

Finally, for the strongest separation, you can make your program a server that exposes the core functionality over an inter-process communication method like HTTP or JSON-RPC, and manage the UIs as separate projects. In the Linux world, this is commonly done using D-Bus.

Pkkm
  • 119
0

The GUI is input and output, as such it is technically irrelevant to the logical functioning of any application.

I work with several web based applications that have a shared convention of having two functions for every page - called Gather and Scatter. Scatter is called to set the initial condition of the page, it creates any data models that the pages uses, sets any bindings and so forth. Gather takes the data from the page and does any additional validation or updates to the submitted data prior to doing the actual work of the page.

I also work with other web based applications that have different conventions, where the data bindings and data flow is handled differently, including a PWA (persistent web application).

What they all have in common is that there is always something between the UI interaction and data persistence. Whether they are MVC, MVVM, MVP, Clean or whatever, nothing mixes the UI and the data code. There is always somewhere where the UI elements have been turned into data and that data is going to be acted on. And that somewhere is always accessible outside of the UI code.

The main functional difference between a CLI application and a GUI is how convenient it is to both collect and display large amount of disparate data.

As long as your code is properly separated, once you have the data, it should be pretty easy.

jmoreno
  • 11,238