Slayer: A Killer Service Layer
Slayer is intended to operate as a minimal service layer for your ruby application. To achieve this, Slayer provides base classes for business logic.
Application Structure
Slayer provides 3 base classes for organizing your business logic: Forms
, Commands
, and Services
. Each of these has a distinct role in your application's structure.
Forms
Slayer::Forms
are objects for wrapping a set of data, usually to be passed as a parameter to a Command
or Service
.
Commands
Slayer::Commands
are the bread and butter of your application's business logic. Commands
are where you compose services, and perform one-off business logic tasks. In our applications, we usually create a single Command
per Controller
endpoint.
Commands
should call Services
, but Services
should never call Commands
.
Services
Services
are the building blocks of Commands
, and encapsulate re-usable chunks of application logic.
Installation
Add this line to your application's Gemfile:
gem 'slayer'
And then execute:
$ bundle
Or install it yourself as:
$ gem install slayer
Rails Integration
While Slayer is independent of any framework, we do offer a first-class integration with Ruby on Rails. To install the Rails extensions, add this line to your application's Gemfile:
gem 'slayer_rails'
And then execute:
$ bundle
And that's it. The integration provides a small handful of features that make your life easier when working with Ruby on Rails.
Form Validations
With slayer_rails
, Slayer::Form
objects are automatically extended with ActiveRecord
validations. You can use the same validations you would on your ActiveRecord
models, but directly on your forms.
Form Creation
With slayer_rails
there are two new methods for instantiating Slayer::Form
objects: from_params
and from_model
. These make it easier to populate forms with data while in your Rails controllers.
Take the following example for a FooController
:
class FooController < ApplicationController
def new
@foo_form = FooForm.new
end
def edit
@foo = Foo.find(params[:id])
@foo_form = FooForm.from_model(@foo)
end
def create
@foo_form = FooForm.from_params(foo_params)
end
def update
@foo_form = FooForm.from_params(foo_params)
end
private
def foo_params
params.require(:foo).permit(:bar, :baz)
end
end
Transactions
Slayer::Command
and Slayer::Service
objects are extended with access to ActiveRecord
transactions. Anywhere in your Command
or Service
objects, you can execute a transaction
block, which will let you bundle database interactions.
class FooCommand < Slayer::Command
def call
transaction do
# => database interactions
end
end
end
Generators
Use generators to make sure your Slayer
objects are always in the right place. slayer_rails
includes generators for Slayer::Form
, Slayer::Command
, and Slayer::Service
objects.
$ bin/rails g slayer:form foo_form
$ bin/rails g slayer:command foo_command
$ bin/rails g slayer:service foo_service
Usage
Commands
Slayer Commands should implement call
, which will pass
or fail
the service based on input. Commands return a Slayer::Result
which has a predictable interface for determining success?
or failure?
, a 'value' payload object, a 'status' value, and a user presentable message
.
# A Command that passes when given the string "foo"
# and fails if given anything else.
class FooCommand < Slayer::Command
def call(foo:)
if foo == "foo"
pass! value: foo, message: "Passing FooCommand"
else
fail! value: foo, message: "Failing FooCommand"
end
end
end
result = FooCommand.call(foo: "foo")
result.success? # => true
result = FooCommand.call(foo: "bar")
result.success? # => false
Forms
Services
Slayer Services are objects that should implement re-usable pieces of application logic or common tasks. To prevent circular dependencies Services are required to declare which other Service classes they depend on. If a circular dependency is detected an error is raised.
In order to enforce the lack of circular dependencies, Service objects can only call other Services that are declared in their dependencies.
class NetworkService < Slayer::Service
def self.post()
...
end
end
class StripeService < Slayer::Service
dependencies NetworkService
def self.pay()
...
NetworkService.post(url: "stripe.com", body: my_payload)
...
end
end
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto 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.
To generate documentation run yard
. To view undocumented files run yard stats --list-undoc
.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/apsislabs/slayer.
License
The gem is available as open source under the terms of the MIT License.