Which approach is better for passing ETags to input ports: as domain objects or as strings?

19 Views Asked by At

I'm working on a project where I need to pass ETags as parameters to input ports, specifically for a method like getDocument(ETag eTag). However, I'm unsure about the best approach for passing ETags to input ports in terms of design and best practices.

Should I pass the ETag as a domain object (ETag) or as a String? Here are some considerations:

  • Semantic meaning: Passing the ETag as a domain object provides a richer and more semantically meaningful representation of the ETag in my codebase. However, passing it as a String may be simpler and more straightforward in some cases.

  • Strong typing and safety: Passing the ETag as a domain object takes advantage of strong typing, which can help prevent errors and improve security by ensuring that only valid ETags are passed to methods. On the other hand, passing it as a String may be less error-prone but lacks the type safety provided by domain objects.

  • Clarity and expressiveness: Passing the ETag as a String may be clearer and more expressive, especially in scenarios where working with domain objects adds unnecessary complexity or confusion.

  • Usability and simplicity: Passing the ETag as a String may be preferable for simplicity and ease of use, particularly if handling domain objects adds unnecessary overhead or if ETag validation is not critical in that context.

Given these considerations, I'm interested to hear from the community: what approach do you recommend for passing ETags to input ports, and what factors should I consider when making this decision? Are there any best practices or design principles I should follow?

ETag is a Value Object. What about Entities, or Aggregates? Should they live inside application and domain layers?

Thanks in advance for your insights!

1

There are 1 best solutions below

0
VoiceOfUnreason On BEST ANSWER

The label for the generalized version of the problem you are describing (whether to pass data using a general purpose data structure or a specialized "object") is Primitive Obsession (from Bad Smells in Code by Kent Beck and Martin Fowler, 1999).

Everyone agrees that you should use "value objects" instead of general purpose data structures when the benefits outweigh the costs. Similarly, everyone agrees that you should use general purpose data structures when the costs of using a "value object" outweigh the benefits.

Disagreements, however, arise in evaluating the costs and benefits, and we don't have a single authority that can give us a Right Answer[tm]. So: "it depends"?


The IANA HTTP Field Registry tells us that the reference for the ETag field is currently RFC 9110. That specification tells us a couple of interesting things:

  • Not any string can be used as an ETag; there's a production rule that restricts the domain of strings that we need to consider
  • The spelling used distinguishes strong and weak validators

The point here being that there are some fine details of ETags that we probably want to hide (in the Parnas 1971 sense); not because the details are likely to change (the http-wg is pretty good about maintaining backwards compatibility in the standards, but the ETag definition did change significantly enough from RFC 2616 to justify a note in the current standard), but because those details aren't relevant.

So rather than having a bunch of parsing/branching of tags mixed into our logic, we might prefer to move that into a separate library.

verb(opaqueString: String) {
   if (ETagLibrary::isWeakValidator(opaqueString))
      ...
}

Now compare to a more abstract-data-type approach

verb(opaqueHandle: ETagLibrary::Handle) {
   if (ETagLibrary::isWeakValidator(opaqueHandle))
      ...
}

There's clearly a downside here, as you've had to perform some ceremony somewhere to get an ETagLibrary::Handle; but as compensation you have the ability to modify the data structure, and in particular you can within that data structure hold cached answers (a general purpose String, in contrast, doesn't offer any convenient place to cache an "isWeakValidator" answer, or other values we could pre-compute during a parsing phase).

You could take the same approach by introducing an ETag domain object as the handle:

verb(eTag: ETag) {
   if (ETagLibrary::isWeakValidator(eTag))
      ...
}

But using the domain object approach supports the introduction of a seam, that allows you to hide the ETagLibrary itself from the code that depends on the answer:

verb(eTag: ETag) {
   if (eTag.isWeakValidator())
      ...
}

In other words, we can change the behavior of verb by modifying the details of the eTag implementation. This could allow us, for example, to use the same verb method with many different implementations of the ETag contract.

So going the value object route gets us both improved cohesion (the details of eTags all in one place), and stability in the client code even when the implementation underlying the contract changes.

Do those benefits offset the costs? In some contexts? yes. In most contexts? maybe. In all contexts? that doesn't seem likely at all.