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…
Domain Series 2: Why You Should Avoid Injecting Closures into Domain Models
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
메타데이터
- 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