Event-driven programming: when is it worth it?

Ok, I know the title of this question is almost identical to When should I use event based programming? but the answers of said question have not helped me in deciding whether I should use events in the particular case I’m facing.

I’m developing a small application. It’s a simple app, and for the most part its functionality is basic CRUD.

Upon certain events (when modifying certain data) the application must write a local copy of said data in a file. I’m not sure about what’s the best way to implement this. I can:

  • Fire events when the data is modified and bind a response (generate the file) to such events. Alternatively, implement the observer pattern. That seems like unnecessary complexity.
  • Call the file-generating code directly from the code that modifies the data. Much simpler, but it seems wrong that the dependency should be this way, that is, it seems wrong that the core functionality of the app (code that modifies data) should be coupled to that extra perk (code that generates a backup file). I know, however, that this app will not evolve to a point at which that coupling poses a problem.

What’s the best approach in this case?

6

Follow the KISS principle: Keep It Simple, Stupid, or the YAGNI principle: You Ain’t Going to Need It.

You can write the code like:

void updateSpecialData() {
    // do the update.
    backupData();
}

Or you can write code like:

void updateSpecialData() {
     // do the update.
     emit SpecialDataUpdated();
}

void SpecialDataUpdatedHandler() {
     backupData();
}

void configureEventHandlers() {
     connect(SpecialDataUpdate, SpecialDataUpdatedHandler);
}

In the absence of a compelling reason to do otherwise, follow the simpler route. Techniques like event handling are powerful, but they increase the complexity of your code. It requires more code to get working, and it makes what happens in your code harder to follow.

Events are very critical in the right situation (imagine trying to do UI programming without events!) But don’t use them when you can KISS or YAGNI instead.

1

The example you describe of a simple data, where the modification triggers some effect can perfectly be implemented with the observer design pattern:

  • this is simpler to implement and maintain than full event driven code.
  • the coupling between subject and observer can be abstract, which facilitates separation of concerns.
  • it’s ideal for one to many relation (subjects have one or many observers).

Event driven approach is worth its investment for more complex scenarios, when many different interactions may occur, in a many-to-many context, or if chain reactions are envisaged (e.g. a subject informs an observer, which in some case wants to modify the subject or other subjects)

2

As you say, events are a great tool to reduce coupling between classes; so while it can involve writing additional code in some languages without built-in support for events, it reduces the complexity of the big picture.

Events are arguably one of the most important tools in OO (According to Alan Kay – Objects communicate by sending and receiving messages). If you use a language which has built-in support for events, or treats functions as first-class citizens, then using them is a no-brainer.

Even in languages without built-in support, the amount of boilerplate for something like the Observer pattern is fairly minimal. You might be able to find a decent generic eventing library somewhere which you can use in all of your applications to minimise boilerplate. (A generic event aggregator or event mediator is useful in almost any kind of application).

Is it worthwhile in a small application? I would say definitely yes.

  • Keeping classes decoupled from each other keeps your class dependency graph clean.
  • Classes without any concrete dependencies can be tested in isolation without consideration for other classes in the tests.
  • Classes without any concrete dependencies require fewer unit tests for complete coverage.

If you’re thinking “Oh but it’s really only a very small application, it doesn’t really matter that much”, consider:

  • Small applications sometimes end up being combined with larger applications later on.
  • Small applications are likely to include at least some logic or components which may later need to be reused in other applications.
  • Requirements for small applications can change, prompting the need to refactor, which is easier when existing code is decoupled.
  • Additional features can be added later, prompting the need to extend existing code, which is also much easier when the existing code is already decoupled.
  • Loosely coupled code generally does not take much longer to write than tightly coupled code; but tightly coupled code takes a lot longer to refactor and test than loosely coupled code.

Overall, the size of an application should not be a deciding factor in whether to keep classes loosely coupled; SOLID principles aren’t just for big applications, they applicable to software and codebases at any scale.

In fact, the time saved in unit testing your loosely-coupled classes in isolation should counter-balance any additional time spent decoupling those classes.

1

The observer pattern can be implemented in a much more smaller fashion than the Wikipedia article (or the GOF book) describes it, assumed your programming languages supports something like “callbacks” or “delegates”. Just pass a callback method into your CRUD code (the observer method, which might be either a generic “write-to-file” method, or an empty one). Instead of “event firing” just call that callback.

The resulting code will be only minimal more complex than calling the file-generating code directly, but without the drawbacks of tight coupling of unrelated components.

That will bring you “best of both worlds”, without sacrificing decoupling for “YAGNI”.

11

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *