Application Logic vs Business Logic in Rails applications
Business logic consists of business domain description and business operations.
Business domain description - a set of domain models and their relations. Business operations (use cases, scenarios) - the logic of how we create, destroy and modify stuff in our system.
As for application logic, it is much easier to start explanation with examples:
- email confirmation field
- consent with some policy
Usually, it is not an integral part of your business domain. It's an implementation detail caused by specifics of the medium.
When we need email confirmation, we won’t store it twice in the database, right? We ask it twice to reduce the chance of mistake in an email.
The same logic applies to captcha. The simple reason you need it is because there are spammers on the internet, and you want to filter them out. It has nothing to do with your business domain.
Why would you put such logic to the core of your system?
Of course the opposite is possible. If you’re a captcha-generating system, then captcha will be a first-class citizen of your business domain.
Alright, let’s try a practical example.
Application logic in Rails models
Let’s say we have a form to implement:
This is a registration form, but our Rails developers' brains immediately convert it to user resource creation in terms of REST.
So, almost instantly, we create this model:
class User < ApplicationRecord validates_presence_of :username, :email, :address validates_confirmation_of :email validates_acceptance_of :terms_of_service end
Well, because Rails gives us this kind of stuff, right?
We could debate if validations are a part of domain model at all, but we won’t.
What we know from the above, is that email confirmation is not a part of business logic here, so it shouldn’t be in your model, if you pretend it to be a domain model.
Not because of the purism, but out of simple practicality.
Because one day you decide to create a user from a console, and you’ll be unpleasantly surprised when you realize that you have to pass both email and email confirmation fields.
What about terms of services?
Well, I always thought that it depends on a situation in this case. But what if you have to import 200 new users, but you don’t have their consent? You’ll be faking their agreement?
If this validation remains in the model, then you will have to eiher fake consents, or hack a model by executing
save(with_validation: false) during the import.
The trick is that the column in database in this case is a part of your domain, but the validation isn’t. Validation - is a scenario-specific applicaion logic here.
If not convinced, imagine this problem at scale. Once you have 100 fields serving different purposes in your model, and a quarter of them will be application-specific, it will be much harder to distinguish them and keep the whole situation under control.
What to do?
Use Shape Objects for application specific validations:
class User < ApplicationRecord validates_presence_of :username, :email, :address end class RegistrationUser < User include ApplicationShape validates_confirmation_of :email validates_acceptance_of :terms_of_service end
What about passwords?
If we use has_secure_password method in the model, then it already forces us to provide password_confirmation.
But the confirmation part is definitely the application logic. What to do now?
Luckily there is an option! Leave this in your model:
has_secure_password validations: false
validates_confirmation_of :password into your Shape object.
You’re done! 🙌
- Domain Model
- Rails ActiveRecord is not (only) ActiveRecord
- Shape Objects