You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/dsl-isomorphic/hyper-operation.md
+195-9Lines changed: 195 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,23 +6,209 @@ Operations are Hyperstack's implementation of Service Object which is...
6
6
7
7
> "A class that performs an action" [A simple explanation of Service Objects for Ruby on Rails](https://medium.freecodecamp.org/service-objects-explained-simply-for-ruby-on-rails-5-a8cc42a5441f)
8
8
9
-
Why do we need Service Objects? Because in any real world system you have logic that does belong in models or stores because it effects multiple models or stores, and it does not belong in components because the logic of the task is independent of the specific user interface design.
9
+
Why do we need Service Objects? Because in any real world system you have logic that does not belong in models (or stores) because it effects multiple models or stores, and it does not belong in components because the logic of the task is independent of the specific user interface design. In MVC frameworks this kind of logic is often shoved in the controller, but it doesn't belong there either.
10
10
11
-
There are also those boundary areas between gathering and processing external data and getting into or out of our stores and models. You don't want that kind of logic in your model or store, so where does it go? Into an operation.
11
+
There are also those boundary areas between gathering and processing external data and getting data into or out of our stores and models. You don't want that kind of logic in your model or store, so where does it go? It belongs in a service object or *Operation* in Hyperstack terminology.
12
12
13
-
Simply put an operation is like a large standalone method that has no internal state of its own. You run an operation, it does it thing, and it returns an answer.
13
+
> The term Operation, the key concepts of the Operation, and a lot of the implementation was taken from [Trailblazer](http://trailblazer.to/guides/trailblazer/2.0/01-operation-basics.html)
14
+
15
+
Simply put an Operation is like a large standalone method that has no internal state of its own. You run an operation, it does it thing, and it returns an answer.
14
16
15
17
Any state that an operation needs to retrieve or save is stored somewhere else: in a model, a store, or even in a remote API. Once the operation completes, it has no memory of its own.
16
18
19
+
Being a stand-alone, glue and business logic method is an Operation's full time mission. The Hyperstack `Operation` base class is therefor structured to make writing this kind of code easy.
20
+
21
+
+ An Operation may take parameters (params) just like any other method;
22
+
+ An Operation may validate the parameters;
23
+
+ An Operation then executes a number of *steps*;
24
+
+ The steps can be part of a success track or a failure track;
25
+
+ The value of the final step is returned to the caller;
26
+
+ And the results can be *broadcast* to any interested parties.
27
+
28
+
Hyperstack's Operations often involve asynchronous methods such as HTTP requests and so Operations always return *Promises*. Likewise each of the steps of an Operation can itself be an asynchronous action, and the Operation class will take care of chaining the promises together for you.
29
+
30
+
Another key feature of Operations is that because they are stateless they make a perfect RPC (Remote Procedure Call) mechanism. So an Operation can be called on the client, but will run on the server, and then return or broadcast the results to the clients. Thus Operations form the underlying data *transport* mechanism between the server and clients.
31
+
32
+
That is a lot to digest, and truly Operations are the swiss-army knife of Hyperstack. So let's dive into some examples.
33
+
34
+
In this simple example we are going to use a third-party API to determine our browser's IP address. First without Operations:
35
+
36
+
```ruby
37
+
classApp < HyperComponent
38
+
before_mount do
39
+
HTTP.get('https://api.ipify.org?format=json').then do |response|
40
+
mutate @ip_address= response.json[:ip]
41
+
end
42
+
end
43
+
44
+
render do
45
+
H1 { "Hello world from Hyperstack your ip address: #{@ip_address}" }
46
+
end
47
+
end
48
+
```
49
+
50
+
Nice and simple. Our App mounts, does a HTTP get from our API, and when it returns it updates the state. The problem is
51
+
our view logic is cluttered up with low level specifics of how to get the address. Lets fix that by moving that logic to a separate service object:
52
+
53
+
```ruby
54
+
classGetIPAddress
55
+
defself.run
56
+
HTTP.get('https://api.ipify.org?format=json').then do |response|
57
+
response.json[:ip]
58
+
end
59
+
end
60
+
end
61
+
```
62
+
63
+
Notice that the object is stateless and because it has no state it is simply a class method. We then use our service object like this:
Our `GetGeoData` uses two remote third party operations, which may occasionally fail so we add a retry
166
+
mechanism. This will introduce four new features of Operation: The *failure track*, *parameters*, and the
167
+
`abort!` and `succeed!` methods.
168
+
169
+
Tracks
170
+
171
+
Operations have two *tracks* of execution. The normal success track which is defined by the `step` method, and a
172
+
*failure track* which is defined by a series of `failed` methods.
173
+
174
+
Execution begins with the first step, and continues with each step until an exception is raised, or a promise fails. When that happens execution jumps *to the next*`failed` step, and the continues executing `failed` steps. The result of the
175
+
Operation will be value of the last failed step, and the Operation's promise will be be rejected (i.e. will be in the fail state.)
176
+
177
+
Parameters
178
+
179
+
Operations can take a series of named parameters defined by the `param` method. Parameters can have type information, defaults, and can be validated. This helps Operations act like a firewall between various parts of the system, making debugging and error handling easier. For now we are just going to use a simple case of a parameter that takes a default value.
180
+
181
+
The `abort!` and `succeed!` methods
182
+
183
+
These provide an early exit like `return`, `break` and next statements. Calling `abort!` and `succeed!` immediately exits the Operation by the appropriate track.
Because this an operation's full time mission the Hyperstack Operation base class is structured to make writing this kind of code easy.
19
210
20
-
An Operation may take parameters (params) just like any other method.
21
-
An Operation may validate the parameters
22
-
An Operation then executes a number of steps
23
-
The value of the final step is returned
24
211
25
-
Hyperstack Operations often involve asynchronous methods such as HTTP requests, and so Operations always return promises
26
212
27
213
28
214
; they orchestrate the interactions between Components, external services, Models, and Stores. Operations provide a tidy place to keep your business logic.
0 commit comments