alki 0.12.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.adoc +132 -20
  4. data/Rakefile +2 -27
  5. data/alki.gemspec +1 -3
  6. data/bin/bundler +17 -0
  7. data/bin/rake +17 -0
  8. data/doc/assemblies.adoc +2 -3
  9. data/doc/assembly_dsl.adoc +191 -30
  10. data/doc/{projects.adoc → executables.adoc} +40 -16
  11. data/doc/index.adoc +4 -3
  12. data/lib/alki.rb +3 -0
  13. data/lib/alki/assembly.rb +4 -34
  14. data/lib/alki/assembly/builder.rb +3 -3
  15. data/lib/alki/assembly/instance.rb +31 -5
  16. data/lib/alki/assembly/instance_builder.rb +48 -0
  17. data/lib/alki/assembly/meta/overlay.rb +7 -2
  18. data/lib/alki/assembly/meta/tags.rb +4 -4
  19. data/lib/alki/assembly/types.rb +9 -0
  20. data/lib/alki/assembly/types/assembly.rb +3 -3
  21. data/lib/alki/assembly/types/group.rb +16 -25
  22. data/lib/alki/assembly/types/original.rb +12 -0
  23. data/lib/alki/assembly/types/override.rb +0 -2
  24. data/lib/alki/assembly/types/service.rb +4 -4
  25. data/lib/alki/circular_reference_error.rb +25 -0
  26. data/lib/alki/dsls/assembly.rb +1 -1
  27. data/lib/alki/dsls/assembly_group.rb +28 -12
  28. data/lib/alki/execution/context_class_builder.rb +1 -1
  29. data/lib/alki/execution/helpers.rb +4 -4
  30. data/lib/alki/execution/overlay_map.rb +37 -0
  31. data/lib/alki/execution/tag_map.rb +42 -0
  32. data/lib/alki/executor.rb +140 -0
  33. data/lib/alki/override_builder.rb +30 -24
  34. data/lib/alki/overrides.rb +4 -0
  35. data/lib/alki/version.rb +1 -1
  36. data/test/feature/mounts_test.rb +15 -0
  37. data/test/feature/multithreading_test.rb +0 -3
  38. data/test/feature/overlays_test.rb +2 -2
  39. data/test/feature/overrides_test.rb +26 -1
  40. data/test/feature/references_test.rb +35 -0
  41. data/test/feature/try_mounts_test.rb +23 -0
  42. data/test/feature/values_test.rb +14 -0
  43. data/test/feature_test_helper.rb +1 -0
  44. data/test/fixtures/example/config/assembly.rb +17 -8
  45. data/test/fixtures/example/config/handlers.rb +10 -5
  46. data/test/fixtures/example/lib/dsls/num_handler.rb +2 -2
  47. data/test/fixtures/example/lib/example/array_output.rb +13 -0
  48. data/test/fixtures/example/lib/example/echo_handler.rb +11 -0
  49. data/test/fixtures/example/lib/example/log_overlay.rb +12 -0
  50. data/test/fixtures/example/lib/example/num_handler.rb +13 -0
  51. data/test/fixtures/example/lib/example/range_handler.rb +13 -0
  52. data/test/fixtures/example/lib/example/switch_handler.rb +11 -0
  53. metadata +39 -44
  54. data/lib/alki/assembly/executor.rb +0 -137
  55. data/test/fixtures/example/lib/array_output.rb +0 -11
  56. data/test/fixtures/example/lib/echo_handler.rb +0 -9
  57. data/test/fixtures/example/lib/log_overlay.rb +0 -10
  58. data/test/fixtures/example/lib/num_handler.rb +0 -11
  59. data/test/fixtures/example/lib/range_handler.rb +0 -11
  60. data/test/fixtures/example/lib/switch_handler.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d65c6ea707a3c2b160cf29edcb6cdd092a9239f3
4
- data.tar.gz: 9ec9b017122f9a6a0cd2c5741835e2a0b0bd570b
3
+ metadata.gz: c3e661fb5cb33a76f94430ca79d62dae03fb2527
4
+ data.tar.gz: 4c288022884681ff99906ac0787cc4c82bb25c1d
5
5
  SHA512:
6
- metadata.gz: ad08a969ea2df6707a7e92139a52d70d1cbf5549d5bf5566dfe11f1253b5b014d2e4db986dd94585db0a85faf9a0aacc93cffcd04d7e01efe41695f940e47216
7
- data.tar.gz: b0ba6db2280081859a064f287b144ec7425de30f0d090370c900c8b0e37c2b56562a4599e9be97f4b2ba6044f9a4fb7f9102db2cb6cb1bec7e1c266777b03f9c
6
+ metadata.gz: c3c44874d4ce17e340d25224b8c5131bc00d4f9595bd09bdf85f22aab2d296a493d1cee68e5f97ffa7794c2e8859bb6bb7cfb18e40c8af5727ecc6777982ba69
7
+ data.tar.gz: f86c11fed68fb98600efb661fb6c1c255212690e18f42bf7fa2bef59df220c5f469d9d2a461a94f7e59c765b0473e605ee7abcd95d6fbbe86758d9a399fdc239
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'alki-testing'
4
+ gem 'rake'
4
5
 
5
6
  gemspec
@@ -1,34 +1,146 @@
1
1
  # What is Alki?
2
2
 
3
- Alki is a small framework for Ruby that provides a powerful dependency injection and program
4
- management system with helpers for testing, creating executables, and writing DSLs.
3
+ Alki is a small library to help organize and scale your Ruby project, so you can focus on the
4
+ important stuff. It can be used alongside frameworks such as Ruby on Rails.
5
5
 
6
- It's goal is to remove uncertainty and friction when building Ruby projects, allowing developers to focus on
7
- implementing business logic. It can be used alongside other frameworks such as Ruby on Rails.
6
+ Some high level features:
8
7
 
9
- Alki tries to combine time tested software engineering concepts such as
10
- https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)[SOLID],
11
- and https://en.wikipedia.org/wiki/Inversion_of_control[Inversion of Control], with
12
- Ruby style ease of use, DSLs, and
13
- https://en.wikipedia.org/wiki/Convention_over_configuration[Convention over Configuration].
8
+ * Easily manage objects and dependencies
9
+ * Enables writing reusable and testable code
10
+ * Developer console (built on pry)
11
+ * Automatic code reloading on changes in development
12
+ * Powerful DSL toolkit
13
+ * Extensible
14
14
 
15
- Further documentation can be found at https://github.com/alki-project/alki/blob/master/doc/index.adoc
16
15
 
17
- Example projects can be found at https://github.com/alki-project/alki-examples
16
+ ## Installation
18
17
 
19
- ## Getting Started
18
+ Add this line to your application's Gemfile:
20
19
 
21
- Install from rubygems
20
+ ```ruby
21
+ gem 'alki'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install alki
31
+
32
+ ## Introduction
33
+
34
+ [NOTE]
35
+ Full "todo" example can be found https://github.com/alki-project/alki-examples/tree/master/todo[here]
36
+
37
+ Alki simplifies project organization by pulling out all of the "connective tissue"
38
+ that connects our classes and modules together, and puts them into a special object
39
+ called an Assembly.
40
+
41
+ There are many ways to use Assemblies, but the most common is to have a single
42
+ Assembly for you project. For example, if you hade a "todo" command line utility
43
+ project that you wanted to use Alki with, all you would need to do to create
44
+ an Assembly is add this file.
22
45
 
23
- gem install alki
46
+ .lib/todo.rb
47
+ ```ruby
48
+ require 'alki'
49
+ Alki.project_assembly!
50
+ ```
24
51
 
25
- Setting up Alki in your project requires only creating a couple of files, but the
26
- `alki` command line tool can also be used to automate the process. Project names
27
- should be given in lowercase using underscores and forward slashes as separators.
52
+ This will create a module called `Todo` that is an empty assembly:
28
53
 
29
- .Example project
30
54
  ```
31
- $ cd <project dir>
32
- $ alki init --addons console <project name>
55
+ $ bundle exec irb -Ilib
56
+ 2.4.0 :001 > require 'todo'
57
+ => true
58
+ 2.4.0 :002 > todo = Todo.new
59
+ => #<Todo:21964520>
60
+ ```
61
+
62
+ ### Defining Elements
63
+
64
+ Add things to the assembly requires an assembly definition file. By convention this is
65
+ named `config/assembly.rb` and is built using a simple DSL. There are
66
+ a handful of different element types in Assemblies. Below are a few of the
67
+ most common. Full documentation of the DSL can be found
68
+ https://github.com/alki-project/alki/blob/master/doc/assembly_dsl.adoc[here]
69
+
70
+ .config/assembly.rb
71
+ ```ruby
72
+ Alki do
73
+ group :settings do <1>
74
+ set(:home) { ENV['HOME'] } <2>
75
+ set(:db_path) { ENV['TODO_DB_PATH'] || File.join(home,'.todo_db2') }
76
+ set :prompt, 'todo> '
77
+ end
78
+
79
+ service :interface do <3>
80
+ require 'todo/readline_interface'
81
+ Todo::ReadlineInterface.new settings.prompt, handler
82
+ end
83
+
84
+ service :handler do
85
+ require 'todo/command_handler'
86
+ Todo::CommandHandler.new db
87
+ end
88
+
89
+ service :db do
90
+ require 'todo/store_db'
91
+ Todo::StoreDb.new file_store
92
+ end
93
+
94
+ service :file_store do
95
+ require 'todo/json_file_store'
96
+ Todo::JsonFileStore.new settings.db_path
97
+ end
98
+ end
33
99
  ```
100
+ <1> `group` allows bundling together subelements and can be moved to their own files
101
+ <2> `set` defines simple values
102
+ <3> `service` defines our main application objects
103
+
104
+ Any element can be accessed directly from the assembly object.
105
+
106
+ ```
107
+ $ bundle exec irb -Ilib
108
+ 2.4.0 :001 > require 'todo'
109
+ => true
110
+ 2.4.0 :002 > todo = Todo.new
111
+ => #<Todo:21964520>
112
+ 2.4.0 :003 > todo.settings.prompt
113
+ => 'todo> '
114
+ ```
115
+
116
+ The 'alki-console' tool can also be used to quickly work with assemblies.
117
+ Add `gem 'alki-console'` to your Gemspec and run `bundle --binstubs`.
118
+
119
+ ```
120
+ $ bin/alki-console
121
+ todo> settings.prompt
122
+ => 'todo> '
123
+ ```
124
+
125
+ ### Creating an executable
126
+
127
+ Read more about creating executables with Alki
128
+ https://github.com/alki-project/alki/blob/master/doc/executables.adoc[here]
129
+
130
+ In the todo example, it's a CLI utility so it requires an executable.
131
+
132
+ .exe/todo
133
+ ```ruby
134
+ require 'todo'
135
+ Todo.new.interface.run
136
+ ```
137
+
138
+ ## Further Documentation
139
+
140
+ Further documentation can be found https://github.com/alki-project/alki/blob/master/doc/index.adoc[here]
141
+
142
+ Example projects can be found https://github.com/alki-project/alki-examples[here]
143
+
144
+ ## Authors
34
145
 
146
+ Written by Matt Edlefsen
data/Rakefile CHANGED
@@ -1,29 +1,4 @@
1
1
  require "bundler/gem_tasks"
2
- require 'rake/testtask'
2
+ require 'alki/testing/tasks'
3
3
 
4
- Rake::TestTask.new do |t|
5
- t.name = 'test:unit'
6
- t.pattern = "test/unit/*_test.rb"
7
- end
8
-
9
- Rake::TestTask.new do |t|
10
- t.name = 'test:feature'
11
- t.pattern = "test/feature/*_test.rb"
12
- end
13
-
14
- Rake::TestTask.new do |t|
15
- t.name = 'test:integration'
16
- t.pattern = "test/integration/*_test.rb"
17
- end
18
-
19
- Rake::TestTask.new do |t|
20
- t.name = 'test:page'
21
- t.pattern = "test/page/*_test.rb"
22
- end
23
-
24
- Rake::TestTask.new do |t|
25
- t.name = 'test'
26
- t.pattern = "test/*/*_test.rb"
27
- end
28
-
29
- task default: [:test]
4
+ task default: [:test]
@@ -18,9 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake", '~> 10.0'
23
- spec.add_dependency "alki-dsl", "~> 0.5"
21
+ spec.add_dependency "alki-dsl", "~> 0.5", '>= 0.5.1'
24
22
  spec.add_dependency "alki-support", "~> 0.7"
25
23
  spec.add_dependency "concurrent-ruby", "~> 1.0"
26
24
  spec.add_dependency "ice_nine", "~> 0.11"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'bundler' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("bundler", "bundler")
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ #
4
+ # This file was generated by Bundler.
5
+ #
6
+ # The application 'rake' is installed as part of a gem, and
7
+ # this file is here to facilitate running it.
8
+ #
9
+
10
+ require "pathname"
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
12
+ Pathname.new(__FILE__).realpath)
13
+
14
+ require "rubygems"
15
+ require "bundler/setup"
16
+
17
+ load Gem.bin_path("rake", "rake")
@@ -17,7 +17,7 @@ Project Assemblies
17
17
  Most of the time, a project will have a single assembly, so Alki makes having a single project wide
18
18
  assembly especially easy.
19
19
 
20
- First. in your project's `lib` directory create a ruby file for your assembly. If your assembly is
20
+ First, in your project's `lib` directory create a ruby file for your assembly. If your assembly is
21
21
  to be called `MyAssembly`, create `lib/my_assembly.rb`. If it's namespaced put it in a subdirectory
22
22
  as usual (i.e. `MyModule::MyAssembly` would go in `lib/my_module/my_assembly.rb`).
23
23
 
@@ -126,7 +126,6 @@ The limitiation of this is that it can only override basic values. To override m
126
126
  a block can be given to `new` allowing the full assembly DSL.
127
127
 
128
128
  ```ruby
129
- require 'alki'
130
129
  class MyLogger
131
130
  def initialize(io)
132
131
  @io = io
@@ -150,4 +149,4 @@ instance.util.logger.info "test"
150
149
 
151
150
  One thing of note is that elements from the assembly are accessible in overrides via the `original`
152
151
  method, as seen above. This can also be used to access the original versions of elements that have
153
- been overriden.
152
+ been overriden.
@@ -2,7 +2,7 @@ Assembly DSL
2
2
  ============
3
3
  :toc:
4
4
 
5
- Building Assemblies is done via a DSL that makes putting together the pieces of your project easy.
5
+ Building Assemblies is done via a small DSL
6
6
 
7
7
  Groups (group)
8
8
  --------------
@@ -23,8 +23,7 @@ puts assembly.new.sub_group.val
23
23
  #output: hello world
24
24
  ```
25
25
 
26
- Scoping is also done by group, so that an element will be found by searching through parent groups until
27
- it is found.
26
+ Scoping is also done by group, so that an element will be found by searching through parent groups.
28
27
 
29
28
  ```ruby
30
29
  require 'alki'
@@ -213,8 +212,7 @@ puts assembly.new.greet "Matt"
213
212
 
214
213
  Services are the key element Assemblies are typically made up of. Like the block form of `set`,
215
214
  `service` takes a name and block, which will be evaluated once on-demand and the result cached.
216
- One difference between the block form of `set` and `service` is that services are affected
217
- by overlays, whereas basic values are not.
215
+ Whereas `set` is a lightweight element for simple values, `service` provides more functionality.
218
216
 
219
217
  Commonly a service will require the file that defines a class, and then constructs an instance of
220
218
  that class.
@@ -235,9 +233,13 @@ assembly.new.logger << "hello\n"
235
233
  ### Factories (factory)
236
234
 
237
235
  Factories are a mix between services and funcs. Like services, they take a block which is evaluated
238
- once. Unlike services though, that block must return a callable object (like a Proc). This object
239
- is then called directly when the factory is referenced. This allows you to require files or construct
240
- a factory object once but still run code on every reference.
236
+ once. Unlike services though, that block must return a callable "builder" (like a Proc).
237
+
238
+ If a factory is referenced as a service (i.e. no arguments) it returns a factory object
239
+ that responds to either `#call` or `#new` and will call the builder in turn.
240
+
241
+ If a factory is instead referenced like a method (i.e. with arguments) it will
242
+ call the builder directly.
241
243
 
242
244
  ```ruby
243
245
  require 'alki'
@@ -249,6 +251,8 @@ assembly = Alki.create_assembly do
249
251
 
250
252
  service :main_logger do
251
253
  logger STDOUT
254
+ # -or-
255
+ logger.call STDOUT
252
256
  end
253
257
  end
254
258
  assembly.new.main_logger << "hello\n"
@@ -256,29 +260,23 @@ assembly.new.main_logger << "hello\n"
256
260
  #output: hello
257
261
  ```
258
262
 
259
- ## Overlays (overlay)
260
-
261
- Overlays are a way to intercept and transform calls made to all services in a given group or it's
262
- sub-groups.
263
-
264
- Overlays are often most useful in groups where all services adhere to a common interface, and overlays
265
- can be used to perform aspect oriented programming like logging, validation, or access controls.
266
-
267
263
 
268
264
  ## Mounting Assemblies (mount)
269
265
 
270
266
  Other assemblies can be mounted into your Assembly using the `mount` command.
271
267
 
272
- The first argument is what the element should be named in the parent assembly. The optional second argument
273
- is the name of the assembly to be mounted. This should be formatted like a require string (relative path but
274
- no `.rb`) and will default to the value of the first argument. If a classified version of that name
275
- can't be found, Alki will attempt to `require` it, and then look for it again.
268
+ The first argument is what the element should be named in the parent assembly.
269
+ The optional second argument is the assembly to be mounted.
270
+ This can either be the assembly module,
271
+ or be a "require" string (relative path but no `.rb`).
272
+ It defaults to the element name.
273
+ If a string, Alki will attempt to `require` it, and then look for a matching constant.
274
+
276
275
 
277
276
  ```ruby
278
277
  require 'alki'
279
278
 
280
- # Creates OtherAssembly
281
- Alki.create_assembly name: 'other_assembly' do
279
+ other_assembly = Alki.create_assembly do
282
280
  set :val, "one"
283
281
 
284
282
  # This is invalid as there is no such element as 'val2'
@@ -293,12 +291,11 @@ Alki.create_assembly name: 'other_assembly' do
293
291
  end
294
292
  end
295
293
 
296
- Alki.create_assembly name: 'main_assembly' do
294
+ assembly = Alki.create_assembly do
297
295
  set :val2, "two"
298
- # Mounts OtherAssembly as 'other'
299
- mount :other, 'other_assembly'
296
+ mount :other, other_assembly
300
297
  end
301
- instance = MainAssembly.new
298
+ instance = assembly.new
302
299
  puts instance.other.val
303
300
  #output: one
304
301
 
@@ -322,22 +319,186 @@ automatically in scope for overrides.
322
319
 
323
320
  ```ruby
324
321
  require 'alki'
325
- Alki.create_assembly name: 'other_assembly' do
322
+ other_assembly = Alki.create_assembly do
326
323
  set :msg, nil
327
324
  func :print do
328
325
  puts msg
329
326
  end
330
327
  end
331
328
 
332
- Alki.create_assembly name: 'main_assembly' do
329
+ assembly = Alki.create_assembly do
333
330
  set :val, "hello"
334
- mount :other, 'other_assembly' do
331
+ mount :other, other_assembly do
335
332
  set :msg do
336
333
  val
337
334
  end
338
335
  end
339
336
  end
340
- MainAssembly.new.other.print
337
+ assembly.new.other.print
341
338
 
342
339
  #output: hello
343
340
  ```
341
+
342
+ ## Overlays and Tags
343
+
344
+ Overlays are a way to transparently wrap services. They work by taking the
345
+ name of a service or a group, in which case they are applied to all services in that group,
346
+ along with the name of an element to be used as the overlay, plus some optional arguments.
347
+
348
+ When the named service is built, the overlay element will be called (with `.call`), with
349
+ the built service object and the optional arguments, and it's result will be what's
350
+ returned.
351
+
352
+ Factories work well as overlay elements.
353
+
354
+ ```ruby
355
+ require 'alki'
356
+
357
+ assembly = Alki.create_assembly do
358
+ overlay :greeting, :exclaim, 3
359
+
360
+ service :greeting do
361
+ 'Hello World'
362
+ end
363
+
364
+ factory :exclaim do
365
+ -> (string,count) do
366
+ string + ('!' * count)
367
+ end
368
+ end
369
+ end
370
+
371
+ puts assembly.new.greeting
372
+
373
+ #output: Hello World!!!
374
+ ```
375
+
376
+ ### Tags (tag)
377
+
378
+ Tags are way of adding metadata to your elements. They can either be just a name, or
379
+ optionally carry a value
380
+
381
+ ```ruby
382
+ require 'alki'
383
+
384
+ assembly = Alki.create_assembly do
385
+ tag :tag1, with_value: 123
386
+ service :tagged do
387
+ meta[:tags]
388
+ end
389
+ end
390
+
391
+ puts assembly.new.tagged
392
+
393
+ #output: {:with_value=>123, :tag1=>true}
394
+ ```
395
+
396
+ Tags can be applied to groups to tag everything within that group
397
+
398
+ ```ruby
399
+ require 'alki'
400
+
401
+ assembly = Alki.create_assembly do
402
+ tag :tag1
403
+ group :grp do
404
+ tag :tag2
405
+ service :tagged do
406
+ meta[:tags]
407
+ end
408
+ end
409
+ end
410
+
411
+ puts assembly.new.grp.tagged
412
+
413
+ #output: {:tag1=>true, :tag2=>true}
414
+ ```
415
+
416
+ #### Overlaying tags (overlay_tag)
417
+
418
+ Instead of overlaying services directly, it's often useful to overlay all services
419
+ with a given tag.
420
+
421
+ ```ruby
422
+ require 'alki'
423
+
424
+ assembly = Alki.create_assembly do
425
+ overlay_tag :exclaimed, :exclaim, 3
426
+
427
+ tag :exclaimed
428
+ service :greeting do
429
+ 'Hello World'
430
+ end
431
+
432
+ factory :exclaim do
433
+ -> (string,count) do
434
+ string + ('!' * count)
435
+ end
436
+ end
437
+ end
438
+
439
+ puts assembly.new.greeting
440
+
441
+ #output: Hello World!!!
442
+ ```
443
+
444
+ Factories can access the tags of the services their being called from, allowing you
445
+ to customize the build based on what tags are present
446
+
447
+ ```ruby
448
+ require 'alki'
449
+
450
+ assembly = Alki.create_assembly do
451
+ overlay_tag :process, :process_string
452
+
453
+ tag :process, exclaim: 3
454
+ service :greeting do
455
+ 'Hello World'
456
+ end
457
+
458
+ factory :process_string do
459
+ -> (string) do
460
+ if exclaim = meta[:tags][:exclaim]
461
+ string = string + ('!' * exclaim)
462
+ end
463
+ string
464
+ end
465
+ end
466
+ end
467
+
468
+ puts assembly.new.greeting
469
+
470
+ #output: Hello World!!!
471
+ ```
472
+
473
+ Finally, tag overlays work even across assembly mounts, allowing overlays to
474
+ be defined in a library, and then applied by tagging services.
475
+
476
+ ```ruby
477
+ require 'alki'
478
+
479
+ string_processor = Alki.create_assembly do
480
+ overlay_tag :process, :process_string
481
+
482
+ factory :process_string do
483
+ -> (string) do
484
+ if exclaim = meta[:tags][:exclaim]
485
+ string = string + ('!' * exclaim)
486
+ end
487
+ string
488
+ end
489
+ end
490
+ end
491
+
492
+ assembly = Alki.create_assembly do
493
+ mount :string_processor, string_processor
494
+
495
+ tag :process, exclaim: 3
496
+ service :greeting do
497
+ 'Hello World'
498
+ end
499
+ end
500
+
501
+ puts assembly.new.greeting
502
+
503
+ #output: Hello World!!!
504
+ ```