DEV Community

Cover image for Understanding Bounded Contexts in Ruby
João Hélio for Marley Spoon

Posted on • Updated on

Understanding Bounded Contexts in Ruby

Introduction

Bounded contexts have a crucial role in managing complexity, maintaining a clear separation of concerns, and ensuring that different parts of a system can communicate effectively without causing conflicts or confusion. Bounded contexts are a fundamental concept in Domain-Driven Design (DDD) and are particularly relevant when building complex systems.

In this article, the bounded contexts will be explored using a practical example in Ruby, where we'll demonstrate how to consume and wrap a Plan object between two different contexts: the Plan Management context and the Sales context.

Understanding Bounded Contexts

A bounded context is a conceptual boundary within which a particular domain model is defined and applicable. Different parts of a software system may have different understandings of specific terms, rules, and behaviors. Bounded contexts help avoid ambiguity and conflicts by isolating other models within their own context, making sure they remain consistent and meaningful within that context.

In our example scenario, imagine a software application for a company that offers subscription plans to its customers. There are two primary bounded contexts: Plan Management and Sales.

  • Plan Management Context: This context is responsible for creating, updating, and managing subscription plans. It focuses on the administrative aspects of plan creation and modification.

  • Sales Context: This context is responsible for handling customer interactions, including the sale of subscription plans. It deals with pricing, availability, and customer-specific details.

Image description

Consuming and Wrapping Plan Objects

Let's dive into the example scenario to understand how bounded contexts work in Ruby. We'll begin by creating a simplified Plan class that holds basic plan information. Then, we'll demonstrate how to consume and wrap a Plan object between the two contexts.

# Plan class in the Plan Management context
module PlanManagement
  class Plan
    attr_reader :id, :name, :price

    def initialize(id, name, price)
      @id = id
      @name = name
      @price = price
    end

    def update_price(new_price)
      @price = new_price
    end
  end
end
Enter fullscreen mode Exit fullscreen mode
# Plan class in the Sales context
module Sales
  class Plan
    attr_reader :customer, :plan_dto

    def initialize(customer, plan_dto)
      @customer = customer
      @plan_dto = plan_dto
    end

    def calculate_discount
      (customer.credit / plan_dto.price) * 100
    end

    def name
      plan_dto.name.upcase
    end

    def price
      plan_dto.price - customer.credit
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

In this example, we have two classes defined in different contexts, the Plan class in the Plan Management context and the Plan in the Sales context. Each class is tailored to its respective context's needs.

To maintain the separation between the contexts and ensure smooth communication, we need to wrap Plan objects when transitioning between contexts. Let's illustrate how this can be done:


# Plan Management context
plan = PlanManagement::Plan.new(1, "Basic Plan", 10.0)

# Sales context
customer = Customer.new("John Doe")
sales_plan = Sales::Plan.new(customer, plan)

# Example usage in the Sales context
discount = sales_plan.calculate_discount
puts "Customer #{customer.name} gets a discount of #{discount}% on the #{sales_plan.name} plan."

puts "Plan details:"
puts "Name: #{sales_plan.name}"
puts "Price: $#{sales_plan.price}"
Enter fullscreen mode Exit fullscreen mode

In this example, the Sales::Plan class provides a bridge between the Sales context and the Plan Management context. It wraps the Plan object from the Plan Management context and exposes only the methods relevant to the Sales context, such as calculating discounts based on a customer's profile.

Conclusion

By defining boundaries around different domain models and using wrapper classes when transitioning between contexts, we can maintain consistency, reduce conflicts, and promote a better understanding of how other parts of the system interact. In our Ruby example, we've demonstrated how bounded contexts can be applied to a Plan object, showcasing the practical benefits of this concept in real-world software development.

References

  • Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans This book is often considered the authoritative source on Domain-Driven Design (DDD) and introduces the concept of bounded contexts along with various other DDD principles.

  • Bounded Context - Martin Fowler's blog is a valuable resource for software design patterns and concepts. His article on bounded contexts provides a comprehensive explanation with examples.
    Link: https://martinfowler.com/bliki/BoundedContext.html

Top comments (0)