temporal-ruby 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2251469f57d99966b2d8defc3d2c154452e61a2aa59ab2e0be2b00179e040f5b
4
+ data.tar.gz: 65b37e1419350839855b19a6078f5329c7912ce4b4520b4e9eb08af82c4c0b8b
5
+ SHA512:
6
+ metadata.gz: c6a358a66d81f648303041d613162c4d5614b89874cd26e6a913b65adc65f0dcc2e51f525528ece916d5b79985d8ab68f8a1591acc60899b25e8b04388ee3212
7
+ data.tar.gz: 22be33cdf432541184b5dfd237f42054141c4c2e5856e90d853c435db7d9f5e13b95d4837d0f26bdc773453200f733c6d332c0689cd574712cf61b24d4247f47
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2020 Coinbase, Inc.
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,461 @@
1
+ # Ruby worker for Temporal [Under Development]
2
+
3
+ <img src="./assets/temporal_logo.png" width="250" align="right" alt="Temporal" />
4
+
5
+ A pure Ruby library for defining and running Temporal workflows and activities.
6
+
7
+ To find more about Temporal please visit <https://temporal.io/>.
8
+
9
+
10
+ ## Getting Started
11
+
12
+ *NOTE: Make sure you have both Temporal and TChannel Proxy up and running. Head over to
13
+ [this section](#installing-dependencies) for installation instructions.*
14
+
15
+ Clone this repository:
16
+
17
+ ```sh
18
+ > git clone git@github.com:coinbase/temporal-ruby.git
19
+ ```
20
+
21
+ Include this gem to your `Gemfile`:
22
+
23
+ ```ruby
24
+ gem 'temporal-ruby', path: 'path/to/a/cloned/temporal-ruby/'
25
+ ```
26
+
27
+ Define an activity:
28
+
29
+ ```ruby
30
+ class HelloActivity < Temporal::Workflow
31
+ def execute(name)
32
+ puts "Hello #{name}!"
33
+
34
+ return
35
+ end
36
+ end
37
+ ```
38
+
39
+ Define a workflow:
40
+
41
+ ```ruby
42
+ require 'path/to/hello_activity'
43
+
44
+ class HelloWorldWorkflow < Temporal::Workflow
45
+ def execute
46
+ HelloActivity.execute!('World')
47
+
48
+ return
49
+ end
50
+ end
51
+ ```
52
+
53
+ Configure your Temporal connection:
54
+
55
+ ```ruby
56
+ Temporal.configure do |config|
57
+ config.host = 'localhost'
58
+ config.port = 6666 # this should point to the tchannel proxy
59
+ config.domain = 'ruby-samples'
60
+ config.task_list = 'hello-world'
61
+ end
62
+ ```
63
+
64
+ Register domain with the Temporal service:
65
+
66
+ ```ruby
67
+ Temporal.register_domain('ruby-samples', 'A safe space for playing with Temporal Ruby')
68
+ ```
69
+
70
+ Configure and start your worker process:
71
+
72
+ ```ruby
73
+ require 'temporal/worker'
74
+
75
+ worker = Temporal::Worker.new
76
+ worker.register_workflow(HelloWorldWorkflow)
77
+ worker.register_activity(HelloActivity)
78
+ worker.start
79
+ ```
80
+
81
+ And finally start your workflow:
82
+
83
+ ```ruby
84
+ require 'path/to/hello_world_workflow'
85
+
86
+ Temporal.start_workflow(HelloWorldWorkflow)
87
+ ```
88
+
89
+ Congratulation you've just created and executed a distributed workflow!
90
+
91
+ To view more details about your execution, point your browser to
92
+ <http://localhost:8088/domain/ruby-samples/workflows?range=last-3-hours&status=CLOSED>.
93
+
94
+ There are plenty of [runnable examples](examples/) demonstrating various features of this library
95
+ available, make sure to check them out.
96
+
97
+
98
+ ## Installing dependencies
99
+
100
+ In order to run your Ruby workers you need to have the Temporal service and the TChannel Proxy
101
+ running. Below are the instructions on setting these up:
102
+
103
+ ### Temporal
104
+
105
+ Temporal service handles all the persistence, fault tolerance and coordination of your workflows and
106
+ activities. To set it up locally, download and boot the Docker Compose file from the official repo:
107
+
108
+ ```sh
109
+ > curl -O https://raw.githubusercontent.com/temporalio/temporal/master/docker/docker-compose.yml
110
+
111
+ > docker-compose up
112
+ ```
113
+
114
+ ### TChannel Proxy
115
+
116
+ Right now the Temporal service only communicates with the workers using Thrift over TChannel.
117
+ Unfortunately there isn't a working TChannel protocol implementation for Ruby, so in order to
118
+ connect to the Temporal service a simple proxy was created. You can run it using:
119
+
120
+ ```sh
121
+ > cd proxy
122
+
123
+ > bin/proxy
124
+ ```
125
+
126
+ The code and detailed instructions can be found [here](proxy/).
127
+
128
+
129
+ ## Workflows
130
+
131
+ A workflow is defined using pure Ruby code, however it should contain only a high-level
132
+ deterministic outline of the steps (their composition) that need to be executed to complete a
133
+ workflow. The actual work should be defined in your activities.
134
+
135
+ *NOTE: Keep in mind that your workflow code can get run multiple times (replayed) during the same
136
+ execution, which is why it must NOT contain any non-deterministic code (network requests, DB
137
+ queries, etc) as it can break your workflows.*
138
+
139
+ Here's an example workflow:
140
+
141
+ ```ruby
142
+ class RenewSubscriptionWorkflow < Temporal::Workflow
143
+ def execute(user_id)
144
+ subscription = FetchUserSubscriptionActivity.execute!(user_id)
145
+ subscription ||= CreateUserSubscriptionActivity.execute!(user_id)
146
+
147
+ return if subscription[:active]
148
+
149
+ ChargeCreditCardActivity.execute!(subscription[:price], subscription[:card_token])
150
+
151
+ RenewedSubscriptionActivity.execute!(subscription[:id])
152
+ SendSubscriptionRenewalEmailActivity.execute!(user_id, subscription[:id])
153
+ rescue CreditCardNotChargedError => e
154
+ CancelSubscriptionActivity.execute!(subscription[:id])
155
+ SendSubscriptionCancellationEmailActivity.execute!(user_id, subscription[:id])
156
+ end
157
+ end
158
+ ```
159
+
160
+ In this simple workflow we are checking if a user has an active subscription and then attempt to
161
+ charge their credit card to renew an expired subscription, notifying the user of the outcome. All
162
+ the work is encapsulated in activities, while the workflow itself is responsible for calling the
163
+ activities in the right order, passing values between them and handling failures.
164
+
165
+ There is a couple of ways to execute an activity from your workflow:
166
+
167
+ ```ruby
168
+ # Calls the activity by its class and blocks the execution until activity is
169
+ # finished. The return value of your activity will get assigned to the result
170
+ result = MyActivity.execute!(arg1, arg2)
171
+
172
+ # Here's a non-blocking version of the execute, returning back the future that
173
+ # will get fulfilled when activity completes. This approach allows modelling
174
+ # asynchronous workflows with activities executed in parallel
175
+ future = MyActivity.execute(arg1, arg2)
176
+ result = future.get
177
+
178
+ # Full versions of the calls from above, but has more flexibility (shown below)
179
+ result = workflow.execute_activity!(MyActivity, arg1, arg2)
180
+ future = workflow.execute_activity(MyActivity, arg1, arg2)
181
+
182
+ # In case your workflow code does not have access to activity classes (separate
183
+ # process, activities implemented in a different language, etc), you can
184
+ # simply reference them by their names
185
+ workflow.execute_activity('MyActivity', arg1, arg2, options: { domain: 'my-domain', task_list: 'my-task-list' })
186
+ ```
187
+
188
+ Besides calling activities workflows can:
189
+
190
+ - Use timers
191
+ - Receive signals
192
+ - Execute other (child) workflows [not yet implemented]
193
+ - Respond to queries [not yet implemented]
194
+
195
+
196
+ ## Activities
197
+
198
+ An activity is a basic unit of work that performs the desired action (potentially causing
199
+ side-effects). It can return a result or raise an error. It is defined like so:
200
+
201
+ ```ruby
202
+ class CloseUserAccountActivity < Temporal::Activity
203
+ class UserNotFound < Temporal::ActivityException; end
204
+
205
+ def execute(user_id)
206
+ user = User.find_by(id: user_id)
207
+
208
+ raise UserNotFound, 'User with specified ID does not exist' unless user
209
+
210
+ user.close_account
211
+ user.save
212
+
213
+ AccountClosureEmail.deliver(user)
214
+
215
+ return
216
+ end
217
+ end
218
+ ```
219
+
220
+ It is important to make your activities **idempotent**, because they can get retried by Temporal (in
221
+ case a timeout is reached or your activity has thrown an error). You normally want to avoid
222
+ generating additional side effects during subsequent activity execution.
223
+
224
+ To achieve this there are two methods (returning a UUID token) available from your activity class:
225
+
226
+ - `activity.run_idem` — unique within for the current workflow execution (scoped to run_id)
227
+ - `activity.workflow_idem` — unique across all execution of the workflow (scoped to workflow_id)
228
+
229
+ Both tokens will remain the same across multiple retry attempts of the activity.
230
+
231
+ ### Asynchronous completion
232
+
233
+ When dealing with asynchronous business logic in your activities, you might need to wait for an
234
+ external event to complete your activity (e.g. a callback or a webhook). This can be achieved by
235
+ manually completing your activity using a provided `async_token` from activity's context:
236
+
237
+ ```ruby
238
+ class AsyncActivity < Temporal::Activity
239
+ def execute(user_id)
240
+ user = User.find_by(id: user_id)
241
+
242
+ # Pass the async_token to complete your activity later
243
+ ExternalSystem.verify_user(user, activity.async_token)
244
+
245
+ activity.async # prevents activity from completing immediately
246
+ end
247
+ end
248
+ ```
249
+
250
+ Later when a confirmation is received you'll need to complete your activity manually using the token
251
+ provided:
252
+
253
+ ```ruby
254
+ Temporal.complete_activity(async_token, result)
255
+ ```
256
+
257
+ Similarly you can fail the activity by calling:
258
+
259
+ ```ruby
260
+ Temporal.fail_activity(async_token, MyError.new('Something went wrong'))
261
+ ```
262
+
263
+ This doesn't change the behaviour from the workflow's perspective — as any other activity the result
264
+ will be returned or an error raised.
265
+
266
+ *NOTE: Make sure to configure your timeouts accordingly and not to set heartbeat timeout (off by
267
+ default) since you won't be able to emit heartbeats and your async activities will keep timing out.*
268
+
269
+ Similar behaviour can also be achieved in other ways (one which might be more preferable in your
270
+ specific use-case), e.g.:
271
+
272
+ - by polling for a result within your activity (long-running activities with heartbeat)
273
+ - using retry policy to keep retrying activity until a result is available
274
+ - completing your activity after the initial call is made, but then waiting on a completion signal
275
+ from your workflow
276
+
277
+
278
+ ## Worker
279
+
280
+ Worker is a process that communicates with the Temporal server and manages Workflow and Activity
281
+ execution. To start a worker:
282
+
283
+ ```ruby
284
+ require 'temporal/worker'
285
+
286
+ worker = Temporal::Worker.new
287
+ worker.register_workflow(HelloWorldWorkflow)
288
+ worker.register_activity(SomeActivity)
289
+ worker.register_activity(SomeOtherActivity)
290
+ worker.start
291
+ ```
292
+
293
+ A call to `worker.start` will take over the current process and will keep it unning until a `TERM`
294
+ or `INT` signal is received. By only registering a subset of your workflows/activities with a given
295
+ worker you can split processing across as many workers as you need.
296
+
297
+
298
+ ## Starting a workflow
299
+
300
+ All communication is handled via Temporal service, so in order to start a workflow you need to send a
301
+ message to Temporal:
302
+
303
+ ```ruby
304
+ Temporal.start_workflow(HelloWorldWorkflow)
305
+ ```
306
+
307
+ Optionally you can pass input and other options to the workflow:
308
+
309
+ ```ruby
310
+ Temporal.start_workflow(RenewSubscriptionWorkflow, user_id, options: { workflow_id: user_id })
311
+ ```
312
+
313
+ Passing in a `workflow_id` allows you to prevent concurrent execution of a workflow — a subsequent
314
+ call with the same `workflow_id` will always get rejected while it is still running, raising
315
+ `TemporalThrift::WorkflowExecutionAlreadyStartedError`. You can adjust the behaviour for finished
316
+ workflows by supplying the `workflow_id_reuse_policy:` argument with one of these options:
317
+
318
+ - `:allow_failed` will allow re-running workflows that have failed (terminated, cancelled, timed out or failed)
319
+ - `:allow` will allow re-running any finished workflows both failed and completed
320
+ - `:reject` will reject any subsequent attempt to run a workflow
321
+
322
+
323
+ ## Execution Options
324
+
325
+ There are lots of ways in which you can configure your Workflows and Activities. The common ones
326
+ (domain, task_list, timeouts and retry policy) can be defined in one of these places (in the order
327
+ of precedence):
328
+
329
+ 1. Inline when starting or registering a workflow/activity (use `options:` argument)
330
+ 2. In your workflow/activity class definitions by calling a class method (e.g. `domain 'my-domain'`)
331
+ 3. Globally, when configuring your Temporal library via `Temporal.configure`
332
+
333
+
334
+ ## Breaking Changes
335
+
336
+ Since the workflow execution has to be deterministic, breaking changes can not be simply added and
337
+ deployed — this will undermine the consistency of running workflows and might lead to unexpected
338
+ behaviour. However, breaking changes are often needed and these include:
339
+
340
+ - Adding new activities, timers, child workflows, etc.
341
+ - Remove existing activities, timers, child workflows, etc.
342
+ - Rearranging existing activities, timers, child workflows, etc.
343
+ - Adding/removing signal handlers
344
+
345
+ In order to add a breaking change you can use `workflow.has_release?(release_name)` method in your
346
+ workflows, which is guaranteed to return a consistent result whether or not it was called prior to
347
+ shipping the new release. It is also consistent for all the subsequent calls with the same
348
+ `release_name` — all of them will return the original result. Consider the following example:
349
+
350
+ ```ruby
351
+ class MyWorkflow < Temporal::Workflow
352
+ def execute
353
+ ActivityOld1.execute!
354
+
355
+ workflow.sleep(10)
356
+
357
+ ActivityOld2.execute!
358
+
359
+ return
360
+ end
361
+ end
362
+ ```
363
+
364
+ which got updated to:
365
+
366
+ ```ruby
367
+ class MyWorkflow < Temporal::Workflow
368
+ def execute
369
+ Activity1.execute!
370
+
371
+ if workflow.has_release?(:fix_1)
372
+ ActivityNew1.execute!
373
+ end
374
+
375
+ workflow.sleep(10)
376
+
377
+ if workflow.has_release?(:fix_1)
378
+ ActivityNew2.execute!
379
+ else
380
+ ActivityOld.execute!
381
+ end
382
+
383
+ if workflow.has_release?(:fix_2)
384
+ ActivityNew3.execute!
385
+ end
386
+
387
+ return
388
+ end
389
+ end
390
+ ```
391
+
392
+ If the release got deployed while the original workflow was waiting on a timer, `ActivityNew1` and
393
+ `ActivityNew2` won't get executed, because they are part of the same change (same release_name),
394
+ however `ActivityNew3` will get executed, since the release wasn't yet checked at the time. And for
395
+ every new execution of the workflow — all new activities will get executed, while `ActivityOld` will
396
+ not.
397
+
398
+ Later on you can clean it up and drop all the checks if you don't have any older workflows running
399
+ or expect them to ever be executed (e.g. reset).
400
+
401
+ *NOTE: Releases with different names do not depend on each other in any way.*
402
+
403
+ ## Testing
404
+
405
+ It is crucial to properly test your workflows and activities before running them in production. The
406
+ provided testing framework is still limited in functionality, but will allow you to test basic
407
+ use-cases.
408
+
409
+ The testing framework is not required automatically when you require `temporal-ruby`, so you have to
410
+ do this yourself (it is strongly recommended to only include this in your test environment,
411
+ `spec_helper.rb` or similar):
412
+
413
+ ```ruby
414
+ require 'temporal/testing'
415
+ ```
416
+
417
+ This will allow you to execute workflows locally by running `HelloWorldWorkflow.execute_locally`.
418
+ Any arguments provided will forwarded to your `#execute` method.
419
+
420
+ In case of a higher level end-to-end integration specs, where you need to execute a Temporal workflow
421
+ as part of your code, you can enable local testing:
422
+
423
+ ```ruby
424
+ Temporal::Testing.local!
425
+ ```
426
+
427
+ This will treat every `Temporal.start_workflow` call as local and perform your workflows inline. It
428
+ also works with a block, restoring the original mode back after the execution:
429
+
430
+ ```ruby
431
+ Temporal::Testing.local! do
432
+ Temporal.start_workflow(HelloWorldWorkflow)
433
+ end
434
+ ```
435
+
436
+ Make sure to check out [example integration specs](examples/specs/integration) for more details.
437
+
438
+
439
+ ## TODO
440
+
441
+ There's plenty of work to be done, but most importanly we need:
442
+
443
+ - Write specs for everything
444
+ - Implement support for missing features
445
+
446
+
447
+ ## LICENSE
448
+
449
+ Copyright 2020 Coinbase, Inc.
450
+
451
+ Licensed under the Apache License, Version 2.0 (the "License");
452
+ you may not use this file except in compliance with the License.
453
+ You may obtain a copy of the License at
454
+
455
+ http://www.apache.org/licenses/LICENSE-2.0
456
+
457
+ Unless required by applicable law or agreed to in writing, software
458
+ distributed under the License is distributed on an "AS IS" BASIS,
459
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
460
+ See the License for the specific language governing permissions and
461
+ limitations under the License.
File without changes
@@ -0,0 +1,3 @@
1
+ module Temporal
2
+ VERSION = '0.0.0'.freeze
3
+ end
@@ -0,0 +1,16 @@
1
+ require_relative './lib/temporal/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'temporal-ruby'
5
+ spec.version = Temporal::VERSION
6
+ spec.authors = ['Anthony Dmitriyev']
7
+ spec.email = ['anthony.dmitriyev@coinbase.com']
8
+
9
+ spec.summary = 'Temporal Ruby client'
10
+ spec.description = 'A Ruby SDK for implementing Temporal workflows and activities in Ruby'
11
+ spec.homepage = 'https://github.com/coinbase/temporal-ruby'
12
+ spec.license = 'Apache-2.0'
13
+
14
+ spec.require_paths = ['lib']
15
+ spec.files = %w(lib/temporal/version.rb lib/temporal-ruby.rb temporal.gemspec Gemfile LICENSE README.md)
16
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: temporal-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Dmitriyev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Ruby SDK for implementing Temporal workflows and activities in Ruby
14
+ email:
15
+ - anthony.dmitriyev@coinbase.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - Gemfile
21
+ - LICENSE
22
+ - README.md
23
+ - lib/temporal-ruby.rb
24
+ - lib/temporal/version.rb
25
+ - temporal.gemspec
26
+ homepage: https://github.com/coinbase/temporal-ruby
27
+ licenses:
28
+ - Apache-2.0
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubygems_version: 3.1.2
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Temporal Ruby client
49
+ test_files: []