Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What do folks use app/services/ in rails applications

Every now and them I would come across this in the ruby on rails ecosystem:

class LocalizeUrlService
class Services::UpdateUserRegistrationForOrder
class ProductionOrderEmailService
UserCart::PromotionsService.new(
Shipping::BulkTrackingService.new(bulk_update, current_spree_user)

You can also see an example here

However, in the official examples of for example "Ruby On Rails Guides" I've never seen this. Which leads me to believe this is a concept coming from another language/paradigm different to Rails/OOP.

Where is this paradigm/trend coming from? Is there a tutorial/book that these folks got influenced by? Are these folks holdouts from the SOA trend of a few years ago?

Is it a good idea to put code in app/service/blah_service.rb ? If yes, what logic/code can be considered "Service" material. Is there any kind code that would/wouldnt belong as a service?

What gem/plugin creates the app/services folder? The vanilla rails app doesn't ship with it at first.

sidetone: Personally I have issue with instantiating a service. I feel classes and instantiating is misused by a ametuer programmers. I feel a class and instantiating is "for a thing" and a service is something that "does" so mixins/defs/include should be the way to go I feel.

like image 927
american-ninja-warrior Avatar asked Mar 05 '23 08:03

american-ninja-warrior


1 Answers

Service objects are for things that don't fit well in the normal MVC paradigm. They're typically for business logic that would otherwise make your models or controllers too fat. Typically they have no state (that's held in a model) and do things like speak to APIs or other business logic. Service objects let you keep your models thin and focused, and each service object is also thin and focused on doing one thing.

Rails Service Objects: A Comprehensive Guide has examples of using service objects to manage talking to Twitter, or encapsulating complex database transactions which might cross multiple models.

Service Objects in Ruby on Rails…and you shows creating a service object to manage the new user registration process.

The EngineYard blog posted Using Services to Keep Your Rails Controllers Clean and DRY with an example of a service object which does credit card processing.

If you're looking for the origins, Service objects in Rails will help you design clean and maintainable code. Here's how. is from 2014 when they were coming on the scene.

Services has the benefit of concentrating the core logic of the application in a separate object, instead of scattering it around controllers and models.

The common characteristic among all services is their lifecycle:

  • accept input
  • perform work
  • return result

If this sounds an awful lot like what a function does, you're right! They even go so far as to recommend using call as the public method name on the service, just like a Proc. You can think of service objects as a way to name and organize what would otherwise be a big subroutine.

Anatomy of a Rails Service Object addresses the difference between a service object and a concern. It covers the advantages a service object has over modules. It goes into some detail about what makes a good service object including...

  • Do not store state
  • Use instance methods, not class methods
  • There should be very few public methods
  • Method parameters should be value objects, either to be operated on or needed as input
  • Methods should return rich result objects and not booleans
  • Dependent service objects should be accessible via private methods, and created either in the constructor or lazily

For example, if you have an application which subscribes users to lists that might be three models: User, List, Subscription.

class List
  has_many :subscriptions
  has_many :users, through: :subscriptions  
end

class User
  has_many :subscriptions
  has_many :lists, through: :subscriptions
end

class Subscription
  belongs_to :user
  belongs_to :list
end

The process of adding and removing users to and from lists is easy enough with the basic create and destroy methods and associations and maybe a few callbacks.

Now your boss wants an elaborate subscription process that does extensive logging, tracks statistics, sends notifications to Slack and Twitter, sends emails, does extensive validations... now what was a simple create and destroy becomes a complex workflow contacting APIs and updating multiple models.

You could write those all as concerns or modules, include all that stuff into these three formerly simple models, and write big Subscription.register and Subscription.remove class methods. Now your subscriptions can Tweet and post to Slack and verify email addresses and perform background checks? Weird. Your models are now bloated with code unrelated to their core functionality.

Instead you can write SubscriptionRegistration and SubscriptionRemove service objects. These can include the ability to Tweet and store statistics and perform background checks and so on (or more likely put that into more service objects). They each have one public method: SubscriptionRegistration.perform(user, list) and SubscriptionRemove.perform(subscription). User, List, and Subscription don't need to know anything about it. Your models stay slim and do one thing. And each of your service objects do one thing.

As to your specific questions...

Where is this paradigm/trend coming from?

As near as I can tell, it's a consequence of the "fat model / skinny controller" trend; that's how I came to it. While that's a good idea, often your models get TOO fat. Even with modules and concerns, it gets to be too much to cram into a single class. That other business logic which would normally bloat a model or controller goes into service objects.

What gem/plugin creates the app/services folder?

You do. Everything in app/ is autoloaded in Rails 5.

like image 90
Schwern Avatar answered Mar 20 '23 14:03

Schwern