← Back to list

Domain Series 2: Why You Should Avoid Injecting Closures into Domain Models

When building robust iOS apps, the design of your domain models plays a crucial role in the health and maintainability of your codebase. A…

abdul ahad · 2025-04-27 13:06 · 1 claps · 3.2 min read paywalled
#anemic-domain-model #rich-domain-model #method-injection #swift
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

Domain Series 2: Why You Should Avoid Injecting Closures into Domain Models

Photo by Ioana Cristiana on Unsplash

Photo by Ioana Cristiana on Unsplash

When building robust iOS apps, the design of your domain models plays a crucial role in the health and maintainability of your codebase. A common mistake developers make is injecting closures or services directly into domain models. This often leads to what’s known as the “Anemic Domain Model” anti-pattern. In this article, we’ll dive deep into why that’s problematic and how you can do better by following a principle known as Method Injection.

The Scenario

Imagine you have a feature where users can comment on images. You might define a model like this:

public struct ImageComment: Equatable {
    public typealias EditCommentCompletion = (Result<ImageComment, Error>) -> Void
    public let id: UUID
    public let message: String
    public let createdAt: Date
    public let username: String

    public var editComment: ((_ request: EditCommentRequest) -> EditCommentCompletion)?
}

Here, the editComment closure is injected directly into the domain model. It seems convenient at first — but it's a trap.

Why Injecting Closures into Domain Models is Bad

When you inject closures into domain models, you are mixing business logic with infrastructure concerns, which leads to a weak, hard-to-maintain architecture.

Let’s walk through a concrete example:

public struct ImageComment {
    public let id: UUID
    public let message: String
    public let createdAt: Date
    public let username: String

    // BAD: Injecting behavior as a closure
    public var editComment: ((_ request: EditCommentRequest) -> Result<ImageComment, Error>)?
}

When you want to edit a comment, you now rely on an external closure:

var comment = ImageComment(id: UUID(), message: "Nice!", createdAt: Date(), username: "Alice")
comment.editComment = { request in
    // Call a web API to update the comment
    // ...
    return .success(ImageComment(id: request.id, message: request.message, createdAt: Date(), username: "Alice"))
}

let result = comment.editComment?(EditCommentRequest(id: comment.id, message: "Updated Comment"))

Problems with This Approach

This approach creates what’s called an Anemic Domain Model, a term coined by Martin Fowler. Anemic models:

  • Contain only data, no real behavior.
  • Force business logic to scatter across the codebase.
  • Make it harder to enforce rules and validate state.
  • Increase fragility and maintenance costs over time.

In essence, you’ve stripped your domain model of its ability to manage its own rules, responsibilities, and constraints.

The Right Way: Rich Domain Models and Method Injection

Instead of injecting behavior, your domain model should own its behavior. For instance, editing a comment should live inside ImageComment, respecting all business rules, like "you can only edit a comment within 12 hours of creation".

Here’s the better design:

public struct ImageComment {
    public let id: UUID
    public private(set) var message: String
    public let createdAt: Date
    public let username: String

    public mutating func edit(newMessage: String, service: ImageCommentService) throws {
        guard Date().timeIntervalSince(createdAt) <= 12 * 60 * 60 else {
            throw EditPeriodExpired()
        }

        let request = EditCommentRequest(id: id, message: newMessage)
        try service.perform(request)
        self.message = newMessage
    }
}

usage:

var comment = ImageComment(id: UUID(), message: "Nice!", createdAt: Date(), username: "Alice")

let service = MyImageCommentService()

do {
    try comment.edit(newMessage: "Updated Comment", service: service)
} catch {
    print("Editing failed: \(error)")
}

What is Method Injection?

According to the book “Dependency Injection: Principles, Practices, Patterns” by Mark Seemann:

Method Injection is when dependencies are passed directly to the method that needs them, rather than through the constructor.

Benefits:

  • Only inject dependencies when needed.
  • Keep models lighter and easier to test.
  • Improve separation of concerns.

Why This Approach Is Better

Simple Analogy

  • Bad (Closure Injection): Imagine buying a TV that only works if you also provide a random external “power-on” button separately. Every home must supply their own.
  • Good (Behavior Inside Model): A good TV has its own built-in “power-on” button and enforces its rules itself.

Conclusion

If you want to build scalable and clean iOS applications:

  • Treat domain models as first-class citizens.
  • Enforce business rules within models.
  • Use Method Injection to bring in dependencies only when needed.

By following these practices, you’ll find your code becomes easier to read, safer to change, and more fun to work with.

Final Thought

“Code is more often read than written. Make sure your domain models tell a story that everyone on your team can understand.”

References

https://martinfowler.com/bliki/AnemicDomainModel.html


메타데이터
post_id
b43e4c9372dd
slug
domain-series-2-why-you-should-avoid-injecting-closures-into-domain-models-b43e4c9372dd
url
https://medium.com/@abdulahd1996/domain-series-2-why-you-should-avoid-injecting-closures-into-domain-models-b43e4c9372dd
canonical_url
https://medium.com/@abdulahd1996/domain-series-2-why-you-should-avoid-injecting-closures-into-domain-models-b43e4c9372dd
author_url
https://medium.com/@abdulahd1996
status
ok
fetched_at
2026-07-09 10:05:04