DEV Community

Cover image for The Request and Response Model in the Clean Architecture
João Hélio for Marley Spoon

Posted on

The Request and Response Model in the Clean Architecture

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)