Shrek
Minimalistic variation of nested builder pattern. The most popular pattern
implementation is in Rack::Middleware
. Extremely useful for organizing
heavy processing and encapsulate every piece of logic.
- Hint: perfect for achieving understanding, how Middleware works
Installation
Add this line to your application's Gemfile:
gem 'shrek'
And then execute:
$ bundle
Or install it yourself as:
$ gem install shrek
Usage
Basic structure is Layer
. Layer is self-sufficient piece of logic, which
in ideal circumstances does not depend on any other Layer.
Each Layer
must have method #call(*args)
, which must return an
array, args
structure in one domain scope does not have to be identical, but
it is highly recommended.
class MyAwesomeLayer < Shrek::Layers
def call(bilder, *args, **) # Argument structure is up to you.
# do something before next layer
args = [bilder, *args, ]
next_layer.call(*args) # Run next execution Layer
# some post execution logic. If you add it, keep in mind, that you have to
# return args for the next Layer
rescue SomeError => e
# We can handle the error here and re-raise it for futher Layers
# in this order we can split messy error handling and keep everything clean
next_layer.skip(2).call(*args) # Skip 2 layers and run
next_layer.skip!(42).call(*args) # Laught version. Will Raise if stack is
# not enough
end
end
You can see more examples
Then you will be able to use desired collection of Layers with
Shrek[MyAwesomeLayer, AnotherOne].call(*initial_arguments)
may be used in own classes
class Paint
include Shrek
end
Paint.new.use_layers FindPaints, SelectWall, PrepareBrushes,
self_return: ->(result) { a }
We also are able to make some tuning with returning value with self_return
option.
It use something, which respond_to :call.
Let's summarize procs and cons of this concept:
Procs
- Maximum encapsulation
- Easy reordering
- Each peace of logic handles only own part of the error
- Readable high level interface with minimal noise
- Standardize interface for one peace of domain logic
- Static analyzers will be happy
- Simple tests
- Well documented by someone else ;)
Cons
- One more abstraction layer
- Can be over-engineering
- Requires more memory
- More code, more files
Why do I need it?
Lets try to put this little snippet of a tale into a code . Given:
Shrek wants to eat something, and he decides to do a slug soup. He got slugs and then went to get some wood for the fireplace. When he came back he put the wood into the fireplace and tried to make fire. But he did not find matches, so he had to move the wood back. He then made shashimi from the slugs and ate as much as he could.He put the rest of the food into the fridge.
It is a simple business story, but think for a while, how will you solve it? And keep in mind, that in the real world a lot of things can go wrong and you want to be able to extend this story, when author decides, that Shrek also has to try to find the lighter or add some swamp spices for better flavor (or onion)
The simplest way is
class ServiceKlass
def initialize(*)
# ...
end
def call
# ...
end
end
But you will have to keep explicit error handler, and resolve all sort of
errors there. You will not be able to share one error between parts of the
service class, so you will do new error names or generalize errors (again, this
pattern is for big and heavy tasks). So the error handler will grow (May be you also
extract it ). At the end you will find yourself with a mess.
Do not believe me? Ok, take default Rails::Middleware
and try to rewrite it
in procedural style this weekend (and do not plan anything else, you will have
lot's of fun ;)
Another way to handle this sort of problem is composite
pattern (the first
example of this pattern, which came to me is ActiveRecord::Migration
). It will
aim at the same goal, but also you will get DSL of the composite
. Also you will
not be able to have the ability of error splitting. Usually it is suitable.
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem to your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/shrek. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Shrek project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.