Introduction
One of the key concepts within the Clean Architecture is the Request and Response model, which helps to separate the core application logic from external concerns such as user interfaces and frameworks. In this article, we'll explore the Request and Response model and demonstrate its implementation using Ruby.
The Request and Response Model
The Request and Response model is a pattern that promotes a clear separation between the inputs and outputs of the application. By doing so, the architecture becomes more adaptable to changes and easier to test, as the core logic is not tightly coupled to any specific framework or external component.
Request
A request in the Clean Architecture represents the input to a use case or an operation within the application. It encapsulates the necessary data required for the operation to be executed. Requests are usually simple data structures and do not contain any business logic. They act as a boundary between the external world (such as UI) and the application's core.
Response
On the other hand, a response represents the output of a use case or operation. It contains the result of the operation, including any data that needs to be presented to the user or propagated to other parts of the application. Similar to requests, responses are also typically simple data structures.
Implementing
Let's consider a simple example of a blog application. We'll implement the Request and Response model for a use case where a user wants to create a new blog post.
In the following example, we define PostRequest
as a Dry::Validation::Contract
and PostResponse
using Dry::Struct
. The CreatePost
use case takes a request object, processes the business logic, and returns a response object.
require 'dry/struct'
require 'dry/validation'
module Blog
module UseCases
module Posts
class PostRequest < Dry::Validation::Contract
params do
required(:title).filled(:string)
required(:content).filled(:string)
required(:author_id).filled(:integer)
end
end
class PostResponse < Dry::Struct
attribute :success, Types::Bool
attribute :message, Types::String
attribute :post_id, Types::Integer.optional
end
class CreatePost
def call(request)
validation_result = PostRequest.new.call(request)
if validation_result.success?
# Business logic to create a new post
# ...
# Return a response
PostResponse.new(success: true, message: 'Post created successfully', post_id: 1)
else
# Return a response with validation errors
PostResponse.new(success: false, message: 'Validation errors', post_id: nil)
end
end
end
end
end
end
# Example usage
request = Blog::UseCases::Posts::PostRequest.new(
title: 'Sample Post',
content: 'This is the content of the post.',
author_id: 123
)
use_case = Blog::UseCases::Posts::CreatePost.new
response = use_case.call(request)
puts response.message
Let's see how we can write a test for the CreatePost
use case:
RSpec.describe Blog::UseCases::Posts::CreatePost do
let(:use_case) { described_class.new }
context 'when the request is valid' do
let(:valid_request) do
Blog::UseCases::Posts::CreatePostRequest.new(
title: 'Sample Post',
content: 'This is the content of the post.',
author_id: 123
)
end
it 'creates a new post' do
response = use_case.call(valid_request)
expect(response.success).to eq(true)
expect(response.message).to eq('Post created successfully')
expect(response.post_id).to eq(1) # Replace with your expected post ID
end
end
context 'when the request is invalid' do
let(:invalid_request) do
Blog::UseCases::Posts::CreatePostRequest.new(
title: '',
content: 'This is the content of the post.',
author_id: nil
)
end
it 'returns validation errors' do
response = use_case.call(invalid_request)
expect(response.success).to eq(false)
expect(response.message).to eq('Validation errors')
expect(response.post_id).to be_nil
end
end
end
To explore the full capabilities of the dry-rb ecosystem check the website.
Benefits of the Request and Response Model
By separating requests and responses, we achieve a clear boundary between external concerns and core business logic and since requests and responses are simple data structures, testing becomes straightforward, and we can easily write unit tests for each use case.
The Clean Architecture, coupled with the Request and Response model, allows us to change external components or frameworks without affecting the core logic.
In conclusion, the Request and Response model is a powerful concept within the Clean Architecture that promotes separation of concerns and maintainability.
References
"Clean Architecture: A Craftsman's Guide to Software Structure and Design" by Robert C. Martin
Top comments (0)