Application Services — 10 common doubts answered

You might have heard about the Domain-Driven Design approach to building applications. In this approach, there is this horizontal layer called Application Service. But what does it do? What arguments does it take? How many operations can it perform? How does it cooperate with other parts of our application such as controllers and models? So many questions, not so many answers. I decided to write down what I imagine this layer to look like based on the books and articles that I’ve read and on my personal feelings after years of experimenting.

In DDD-flavored applications, your domain logic lives mostly in the form of aggregates (and some bits in domain services). But that logic and object’s behavior is usually persistence-agnostic. It means that it is not our domain objects responsibility to worry how they are persisted. So where does it happen? In Application Services. That’s their primary role but not the only one. Let’s dissect Application Services.

Most common responsibility

Application Service get’s domain objects from repositories and saves changed domain objects to repositories.

class OrderExpirationService # ... def call ( order_number ) order_repository . transaction do order = order_repository . find ( order_number , lock: true ) # Load order . expire # business operation order_repository . save ( order ) # save back end end end

If you migrate from Rails-way towards more Domain-Driven approach and you don’t have repositories yet but you continue using ActiveRecord this will look similar to:

class OrderExpirationService def call ( order_number ) Order . transaction do order = Order . lock . find ( order_number ) # Load order . expire # business operation order . save! # save back end end end

One of the simplest rules that you can follow to make your code more testable and easier to refactor in the future is to avoid calling save! (or save ) from your models.

Don’t:

class Order < ApplicationRecord def expire # verify preconditions self . state = "expired" # other logic save! end end

it is not a responsibility of the domain object to save itself. It makes testing harder, slower and does not allow you to easily compose multiple operations without constantly saving to DB.

So even if you use ActiveRecord, just don’t call save! from within the class. Only the application service should do it.

Other responsibilities

The repository is often just one of the dependencies that our code need. Others can be

adapters

message bus

event store

domain services

class OrdersAddProductService def call ( order_number , product_id , quantity ) prices_adapter = ProductPricesApiAdapter . new order_repository . transaction do order = order_repository . find ( order_number , lock: true ) order . add_product ( prices_adapter , product_id , quantity ) order_repository . save ( order ) end end end

Can the application service read more than one object?

I believe it can. But the other object ( Product ) should come from the same bounded context as the object we are updating ( Order ).

class OrdersAddProductService def call ( order_number , product_id , quantity ) Order . transaction do order = Order . lock . find ( order_number ) product = Product . find ( product_id ) order . add_product ( product , quantity ) order . save! end end end

There are opinions saying that it application services should not be doing it and it should only read and update one object. I am not convinced, however.

There is also a fraction claiming that the 2nd object should not be another aggregate but rather a read-model. I believe it can be an aggregate from the same bounded context.

It is not recommended as it increases coupling between objects (aggregates) that are being updated at the same time. Potential issues to consider:

the operation takes longer

the objects have a different lifecycle (one can be updated rarely and by one person, the other can be updated multiple times per second by various users). So the high-throughput nature of one object can cause deadlocks and prevent the whole operation from happening. Or the fact that the operation is longer and both objects remain locked in DB can lower the throughput of the object which is more often edited.

the objects might be persisted in different DBs so the change is not transactional anyway

What should the application service receive as an argument?

I believe it’s best if the service receives a command. The command can be implemented using your preferred solution - pure ruby, dry-rb, virtus, active model, whatever.

class AddProductToOrderCommand attr_accessor :order_number , :product_id , :quantity end class OrdersAddProductService def call ( command ) command . validate! Order . transaction do order = Order . lock . find ( command . order_number ) product = Product . find ( command . product_id ) order . add_product ( product , command . quantity ) order . save! end end end

Having commands can be beneficial if you want to easily see what’s supposed to be provided. It is more explicit, and it makes it more visible. The more attributes are provided by a form or API the more likely having this layer can be valuable. Also, the more complicated/nested/repeated the attributes are, the more grateful you will be for having commands.

Commands can perform simple validations that don’t require any business knowledge. Usually, this is just a trivial thing like making sure a value is present, properly formatted, included in a list of allowed values, etc.

The interesting aspect of defining a closed (not allowing every possible value) structure is that it also increases security and basically acts similar to strong_parameters .

Where should the command be instantiated?

I think the controller fits nicely to the responsibility. It has access to all the data from HTTP layer such as currently logged user id (from a session or a token), routing parameters, form post parameters etc. And from all that, it can build a command by passing the necessary values.

Should the command be implemented in the same layer as application service or a higher layer (above it)

Ideally, I believe the layer which contains application service should define the interface of the command and a higher layer could implement it. What’s the difference?

Command implemented in a higher layer

If we implemented the command in a higher layer (ie controller) then it could use objects it already has access to such as cookies, session, params etc.

class Controller < ApplicationController class MyCommand def initialize ( session , params ) @session = session @params = params end def user_id session . fetch ( :user_id ) end def product_id params . fetch ( :product ). fetch ( :id ) end def command_name "MyCommand" end end def create cmd = MyCommand . new ( session , params ) ApplicationService . new . call ( cmd ) head :ok end end

Because of Ruby’s duck-typing mechanism, this can work but, due to the lack of interfaces, it is not easy for the ApplicationService to describe the exact format it expects the data from the command. For simple commands that’s not a big issue, but the bigger they get and the more nested attributes they include the harder it is.

One thing I considered as a substitute for interface was… linters :) Such as rack linter or this. In other words shared examples distributed as parts of the lower layer (implementing an Application Service) that could be executed in the tests of the higher layer (controller).

RSpec . describe Controller :: MyCommand do include_examples "ApplicationService::MyCommand" end

in case Test Unit frameworks this would be just a module with test methods to include:

class TestMeme < Minitest :: Test include ApplicationService :: MyCommandLint def setup @command = Controller :: MyCommand . new end end

But I didn’t find this separation worthy and I never went that way. I would in a language with interfaces where defining the structure without implementation is pretty fast.

Command implemented in the same layer as the service

In this version, the controller must copy the necessary data to a provided implementation of the command, which might be less convenient when there are more arguments.

class Controller < ApplicationController def create cmd = MyCommand . new cmd . user_id = session . fetch ( :user_id ) cmd . product_id = params . fetch ( :product ). fetch ( :id ) ApplicationService . new . call ( cmd ) head :ok end end

or

class Controller < ApplicationController def create cmd = MyCommand . new ( user_id: session . fetch ( :user_id ) product_id: params . fetch ( :product ). fetch ( :id ) ) ApplicationService . new . call ( cmd ) head :ok end end

Can the application service handle more than 1 operation?

I think it can and I would even say that it’s often beneficial to group many operations together in one Application Service instead of scattering them across multiple classes. Because usually, the use-cases need the same dependencies to finish and have a similar workflow.

class OrdersService def expire ( order_number ) with_order ( order_number ) do | order | # ... end end def add_product ( order_number , product_id , quantity ) with_order ( order_number ) do | order | # ... end end private def with_order ( number ) order_repository . transaction do order = order_repository . find ( number , lock: true ) yield order order_repository . save ( order ) end end end

If you go with commands, you can even hide all those internal methods as private.

class OrdersService def call ( command ) case command when ExpireOrderCommand expire ( command ) when AddProductToOrderCommand add_product ( command ) else raise ArgumentError end end private def expire ( command ) with_order ( command . order_number ) do | order | # ... end end def add_product ( command ) with_order ( command . order_number ) do | order | # ... end end def with_order ( number ) order_repository . transaction do order = order_repository . find ( number , lock: true ) yield order order_repository . save ( order ) end end end

What should the command contain?

The id/uuid of related entities which should be changed

The data necessary for performing those changes

Verified id of the user performing current action current_user_id coming from the controller usually based on session, cookies or tokens

The user id on behalf of whom the action is executed in case that’s not the same as the user performing current action Scenario: An Admin executes an operation on behalf of an user who called customer support and asked for help.



What’s the difference between Application Service and Command Handler

I believe there is none, really. It’s just the names comes from 2 different communities (DDD vs CQRS) but they represent a similar concept. However, Command Handler is more likely to handle just one command :)

What should the Application Service return?

Ideally, nothing. The reason most Application Services return anything is that the higher layer needs the ID of the created record. But if you go with client-side generated (JS fronted or in a controller) UUIDs then the only thing the controller needs to know is whether the operation succeeded. This can be expressed with domain events and/or exceptions.

Should the Application Service contain business logic?

Nope. That should stay in your Aggregates and Domain Services.

Can the application service handle more than 1 operation at the same time?

Yes, although I am not yet sure what’s the best approach for it. I consider a separate command vs a commands container for a batch of smaller commands.

Handling many smaller operations can be useful when there are multiple clients with various needs or when you need to handle less granular (compared to standard UI operations) updates provided via CSV/XLS/XLSX/XML files.

A separate command

class SetDeliveryMethodAndConfirmCommand attr_accessor :order_number , :delivery_method end class OrdersService def call ( command ) case command when SetDeliveryMethodAndConfirmCommand delivery_and_confirm ( command ) when ConfirmCommand confirm ( command ) when SetDeliveryMethodCommand set_delivery ( command ) # ... else raise ArgumentError end end private def delivery_and_confirm ( command ) with_order ( command . order_number ) do | order | order . set_delivery_method ( command . delivery_method ) order . confirm end end # ... end

General commands container

class BatchOfCommands attr_reader :commands def initialize @commands = [] end def add_command ( cmd ) commands << cmd self end end class OrdersService def call ( command ) case command when BatchOfCommands batch ( command . commands ) when ConfirmCommand confirm ( command ) when SetDeliveryMethodCommand set_delivery ( command ) # ... else raise ArgumentError end end private def batch ( commands ) commands . each { | cmd | call ( cmd ) } end # ... end batch = BatchOfCommands . new batch . add_command ( SetDeliveryMethodCommand . new ( ... )). add_command ( ConfirmCommand . new ( ... )) OrdersService . new . call ( batch )

However, this naive approach can lead to a poor performance (reading and writing the same object multiple times) and it does not guarantee processing all commands transactionally. It will be up to your implementation to balance transactionality and performance and choose the model which best covers your application requirements (including non-functional ones). Generally, you can choose from:

one transaction per one operation simplest to implement worst performance short locks on objects

one transaction per one object balanced performance and lock time guaranteed a whole record processed or skipped (in case of crash in the middle of the process)

one transaction per the whole batch very long lock on many objects which might affect many other operations occurring at the same time guaranteed all or none records processed



Here is an example of how the balanced one transaction per one object approach can be implemented by grouping commands related to the same object.

class OrdersService def call ( command ) case command when BatchOfCommands batch ( command . commands ) when ConfirmCommand confirm ( command ) when SetDeliveryMethodCommand set_delivery ( command ) # ... else raise ArgumentError end end private def batch ( commands ) groupped_commands = commands . group_by ( & :order_number ) groupped_commands . each do | order_number , order_commands | with_order ( number ) do order_commands . each { | cmd | call ( cmd ) } end end end def confirm ( command ) with_order ( command . order_number ) do | order | order . confirm end end def with_order ( number ) if @order && @order . number == number yield @order elsif @order && @order . number != number raise "not supported" else begin order_repository . transaction do @order = order_repository . find ( number , lock: true ) yield @order order_repository . save ( @order ) end ensure @order = nil end end end end batch = BatchOfCommands . new batch . add_command ( SetDeliveryMethodCommand . new ( ... )). add_command ( ConfirmCommand . new ( ... )) OrdersService . new . call ( batch )

No matter which approach, you go with, it can be beneficial when transforming your UI from a bunch of fields sent together into more Task Based UI.

Get more

Here are some links worth reading to see other author’s perspective on services:

If you still have some questions and doubts, leave them in a comment or buy our book and join the Rails/DDD community to discuss with other Ruby devs doing DDD.

We wrote many articles about doing DDD in Ruby and Rails such as When DDD clicked for me or My fruitless, previous attempts at not losing history of changes in Rails apps which show our journey into better, prettier code that can handle complex Rails apps.

For a free weekly content about DDD and working with big apps join our newsletter.