📜 ⬆️ ⬇️

Alternative to callback

Let's assume that you need to make a rail application that allows you to create an order, depending on the input data of the order, create one or several services, reserve some resources for these services.

During processing, the order changes its status from new to completed, while creating several services (depending on data) and they must be running and work by the end of order processing. A simple example - you make yourself a SIM card for your cell phone. Voice services, SMS and MMS, mobile Internet (which has its own tariffs), voice mail, caller ID, etc. are connected to this SIM card. By the end of the processing of your contract (order), all these services must be up and running. Then you can conclude additional. contract and switch to other tariffs of the mobile Internet, etc. This is just an example of logic, which I will refer to for clarity.

The absolute majority of programmers will start making such an application on callbacks or triggers. A new order has been created - we set a new state for it - and we hang up a callback which starts creating services, etc. Next, I will try to explain why this is an absolute evil.

Callbacks are not controlled


While in your application there are 10-15 callbacks - you can still somehow control them. As soon as there are more models in the application, the number of callbacks grows. One callback changes the data, because of this, the second callback works, changes other data - the third works, and so on. As a result, it is not easy to restore the whole chain and understand what arose and why. I am already silent about such options as looping callbacks - the first one worked, the second one worked ... the tenth one worked and as a result of its changes the first callback re-worked. Or, the first one worked first, then the third, then the fifth and then the second (the numbering here is conditional - just for clarity). There is not even talk of any controlled sequence of work of callbacks. Each by itself.
')

The history of callbacks


You have a warrant. It was partially processed, after which you rolled out the next update of the application - the logic has changed - the user comes to you and asks the same question - “why is there such a value”. Now you need to understand not only the current code, but also to see which version of the code was used at the time of the expected callback response - there are only more assumptions.

Logic Versions


Suppose you need to change the callback logic. For example, when a condition was fulfilled, one type of service was created, now another. But for already processed orders (those that started on the old logic and not yet fully processed) - you need to keep the old logic.
Your callback is growing - you need to lay a new logic and keep the old one that works for orders that are running, for example, up to a certain number. I had a case when business users tried to complete an order that “stood” unfinished for 7 years. How much logic and code has changed during this time - just try to imagine.

The callbacks are awfully monitored.


When a business user comes to you and asks the question “why does this service have such a value here” - you are faced with the first problem - you need to look at the base, logs, and so on.
Even understanding exactly which callback worked and changed the data you are interested in can be a very difficult task and you can kill more than one day for it. In fact, no matter how many times you dig in the logs - as a result, you can only assume that "this callback worked for some reason." Even if you have logs, as the system grows, you will increasingly fall into assumptions. Perhaps several callbacks worked and set the same value to this model. Then absolutely bad. The complexity of analyzing, reproducing and solving a problem grows exponentially.

Snap to model


My “favorite” sin of kolbek - they are tightly tied to the model. Today you have a TypeA order, tomorrow it was replaced with a TypeB order - and the necessary callbacks did not fire - the order processing chain broke. You can of course fix it - technically this is a code error, but in practice it is better to build your application in such a way that this error could not occur in principle.
Next, we will set 2 order states for you - new and completed and associated callbacks. You need to complicate the logic - add callbacks at the time of processing logic. For example, failed, processing - and add some more callbacks to them. The order can get failed several times during its processing and can go to processing several times - and each time callbacks will work.
Then it turns out that the logic after fails is different and the status of re-processing (for example) appears and the logic becomes even greater. The number of callbacks is growing. At the same time, it is necessary to take into account that the order is paid or not - and these are some more callbacks attached to different parameters of the model. While the order is not paid (paid - one order field, status - another) services cannot be started, etc. - you start to invent new order states, almost-done-not-yet-paid, etc. - you start to invent new and new states so that you can attach logic to them. The combination of states (paid / processed, unpaid / processed, etc.) becomes more and more. Callbacks are more complicated.

No isolation logic


I will try to explain - let's say you have a mobile Internet service. There was a first order for which this service was launched, then there was a second order - a change in the tariff plan - the service is essentially the same, but differently, have a different Internet speed, etc. At callbacks, you cannot isolate the logic associated with the first order from the logic associated with the second order — everything is tied to the service model itself.
Moreover, there is an accounting department - in which the business logic is connected with the payment of a warrant, there is a technical department that has to ensure the work of the Internet - it has a different business logic - and they really should be isolated. At callbacks, everything is zavazano to the order model - and it is no longer possible to separate the processing of accounting and technical department. The accounting department can pay for the service in several payments - several “processes” - while the technical department during the same time can get rid of technical support and some service operations to ensure the same service and all this logic should not be tied to the model .

The conclusion is simple: the application logic should not be tied to the model.

Alternative


The first thing you need to understand - if you have business logic - it means you need to tie it not to the model. There is a flow of order processing - it means an entity is needed, to which all this is tied instead of the model. Without being clever, let's call this entity a “process”.

All business logic is tied to the process - as an option - in the form of operations. Those. technically, you should be able to open the processes that changed the order — and see when they were created, how they worked, which users participated, what changed and what was done, what operations worked (and why) —that is you should have not just speculation - but an exact history of the process. Plus - it would be desirable to be able to look at the processes that are not yet completed - what other operations can be performed as part of the process. If something is broken in the process - you need to correct the data / code - to be able to start the process further (and not change the state of some object through the console, so that some callbacks would work and the process went further).

Further - the user participates in your process. If the application is on callbacks - the user is actually thrown out of logic - went to some order, changed some data - callbacks bummed and the order is further processed. In the case of a process, you can introduce such a thing as “user operation” - and say that some process is waiting for certain user operations, that the following user operations were created and executed during the process. As part of the "user" operation, the user can change the data on several models.

Next, you need some kind of process configuration - the ability to specify which operations it contains, operation dependencies, etc.

The process and operations should have their own data scopes - some intermediate process variables that make it possible not to load models with data needed only by processes. As a result, the models are simpler and easier.

While on this stop. If the topic is interesting, then you can write a lot more. Who cares - see gem rails_workflow and ask questions.

Source: https://habr.com/ru/post/261475/


All Articles