Keeping code simple with services, bottled services

Ok, so welcome to my first blog.
I think we have all been there when we turn up to the task, get a look at the code, and we are confronted with with what seem to be a family of godzilla controllers ready to destroy your motivation and any excitement to work on the project. (The same goes for models too.)

It's like the concept of keeping things DRY just flew out the window and nobody even noticed. Well welcome to services, your saviour in a spiralling hell of spaghetti code and code repetition. Services provide a seperate layer that lives beside the controllers and models, just waiting for someone to call them so they can do their job, their one and only job, because thats right I believe services should not be multi skilled multi-method masterminds, but should adhere to the single responsibility pattern, and just do one thing really well.

After reading several great articles on services in Ruby, some of the more interesting reads being:
Gourmet Service Objects
Improving Large Rails Apps with Service Objects
Anatomy of a Rails Service Object
I found myself using services more and more, and while there are many awesome gems (some described in some of the articles above) that can be applied to make using services easier, I failed to find a simple all-in-one solution to what I wanted to do, so I decided to try and make one myself, to make my development life easier (and hopefully some other peoples too!), a gem I called bottled_services, we'll have a look at how that can help a little later, first lets set the scene.

When you know you need a service

The rails way denotes a fat models, skinny controllers design pattern, but the main problems with this is the moment your project starts to move beyond a simple blog creation app, you can very quickly find your models are moving into the morbidly obese category, and although your controller actions may seem like they are doing their job and are skinny, in my experience with a little scroll down you find you have opened pandora's box and there are streams of private methods being called from before/after callbacks.

Some would say, "but if we want to share code, we always have concerns!". Yes we do, but in my personal experience this will eventually lead to more headache, why? first of all concerns are generally shared only between the same group of classes, ie concerns for controllers, and concerns for models. What happens when we want to be able to call something from several locations? oops. The second big hiccup I find with concerns is that things tend to get very magical very quickly, and implicit behaviour begins to rule the land. I myself am more of an explicit behaviour fanboy, I like to know what is going on straight away at first glance, and this can be easily achieved with services, especially if we follow some good naming conventions.

Lets move away from the idea of fat controllers and skinny models and redefine the base design pattern we want to be using.
Models - we want them skinny, I tend to try to use them like a structure/state reference, containing only relations, validations, and scopes, at most the only methods we want to see in there are state checking methods such as admin?.
Controllers - Again, we want these skinny, responsibilities including handling parameters, sessions, routing, redirecting, and rendering. Business logic of any shape or form should not be the responsibility of the controller.
Services - I bet you will be shocked to hear that we also want these to be skinny. Services should handle all the business logic and changing model states etc. However when we say all the business logic, we're not saying stuff a service with lots of methods and responsibilities, we want to be following the single responsibility pattern, therefore a single service will handle a single job, to ensure maximum reusability, readability, and keep them skinny.
Saying this is the all-in-one solution to everything is extremely naive, and there are many other great patterns that can and should be implemented to improve things even further such as the use of decorators, observers, and singletons. While I strongly recommend using these, they are outside the scope of this post, so we'll leave that for another day.

The problem space

Lets take an example often seen on various SNS channels, events. We have an event that a user can attend, its that simple! (actually our example is purposefully over simplified because I want the focus to remain on the design pattern). For such an example we can assert that we will have a User and Event model related to each other probably by a habtm relationship, or a has many through relationship. A User::EventsController which can show available events for a particular user (index), and create the user attended event relationship (create), hence why this controller is in the User namespace, we are not actually creating the event (we can assume that has already been done in a top level or other namespaced EventsController), but we are creating the relationship to the user.
Before entering any logic to do with a user choosing to attend an event, our code might look something like this:

class User < ApplicationRecord  
    has_and_belongs_to_many :events  
end  
class Event < ApplicationRecord  
    scope :available_for_user, ->(user){ # some query to get events visible to a given user }

    has_and_belongs_to_many :users
end
class User::EventsController < ApplicationController  
    def index  
        @events = Event.available_for_user(@user)  
    end

    def create  
    end  
end

So what we want to do is add functionality so that a User can attend an event, at first it might be tempting to add this into the controllers create action with something along the lines of:

@user.events << @event
@user.save!

Its only two lines, but this is still not part of the controllers responsibilites, all the controller should do is tell something that this needs to be done. Also, if we add in the need to check if the event is at full_capacity or not before trying to allow the user to attend, then the controller will soon get flooded with lots of little checks and logic.

The next obvious approach would perhaps be to add an instance method into the User class such as:
@user.attend_event(@event)

But again if we go by the above rules this falls outside the scope of the models responsibilities, it is neither structure, nor a reference of state, but instead, its a change of state, and part of the business logic. So lets get a sweet service going to handle this and keep our controller and model squeaky clean!

Using bottled_services

Bottled services are intended to make the development process as easy and pain free as possible, we want to be able to make a service just as easily as we make anything else in rails. We can achieve this very easily with the bottled service generator:

rails g bottled_service AttendEvent user:User event:Event

with the above we will get a service that can accept two arguments, one of type User, and one of type Event. The types dont have to be included but they can provide us with an extra net of safety against ourselves when we want to guarantee that an argument is of a particular type. The resulting file will look as follows:

class AttendEvent < BottledService

    att :user, User
    att :event, Event

    def call
        # Do something....
        yield if block_given?
    end
end

lets change our call method to fit our needs:

def call
    return false if @event.full_capacity?
    @user.events << @event
    @user.save!
end

And there we go, simple, does its job, and we made our service to handle it in no time at all. Of course there are other things we should probably be doing, such as not just relying on the events state check, but also provide some form of validation as well as error handling on any raised errors, but as we said earlier we are keeping this simple.

Now all we need to do let it do its thing when it needs to! In our controller we might have something like:

def create
    AttendEvent.(user: @user, event: @event)
    redirect_to an_example_path
end

Notice in the above that the class method call is being called via the .() syntax, this is because all bottled_services inherit a class method call and an initialize method from the BottledService class, which creates an instance of our service with the arguments provided and automatically calls the instance method we define in our generated services for us. If you want to know more about the inner working of bottled services feel free to take a look at the repository.

Conclusion

Well thats the basic first steps on how bottled services can help us with our work flow and code base, I use them all the time and find they improve my work speed, code reusability, and are a great step toward achieving a clean DRY code base, and I hope they can benefit others too. You may have noticed they also have the ability to accept blocks, for more advanced usage and features, but if we through every possible use case and scenario I`ll be typing this for weeks! I therefore encourage you to take a look a read of the repository, whack them in your gemfile, and give them a whirl. As my first gem, I also want to improve it to help the community more, so long as it keeps to the core priorities of bottled services which is to keep things fast, simple, and easy, so I would love any feedback or recommendations, or even contributions. Thanks for reading!

bottled services repository
bottled services rubygems page

SNS

Contact Me

  • john.hayes.reed@gmail.com

Recent Activity

Attended Kansai Ruby Kaigi 2017

A Ruby conference in the kansai region of japan, held in Osaka. Listened to a variety of presentations from many people in the Ruby community. This years theme was Community and Business.

Bottled observers gem release

The first production version of bottled_observers has been released (v0.1.0)


New blog post!

A blog about more advanced decorating concepts in ruby.


Bottled decorators gem update

v0.1.5 of bottled decorators has been released.


New blog post!

A blog post about the concept of class-instance variables


Website Design Update

johnhayesreed.com has had a makeover with Bootstrap 4

New blog post!

A new blog about design patterns in rails - Decorators


Bottled decorators gem release

The first production version of bottled_decorators has been released (v0.1.4)


New blog post!

A new blog about desing patterns in rails - Services


Bottled services gem release

The first production version of bottled_services has been released (v0.1.3)


Ruby Rampage 2016

Took part in 2016's Ruby Rampage 48 hour Hackathon.