Clean Architecture in Ruby: data types?

111 views
Skip to first unread message

Reed Law

unread,
Feb 12, 2015, 10:05:59 PM2/12/15
to clean-code...@googlegroups.com
After implementing Clean Architecture in a large Rails project there is a nice separation between the framework and business logic. But one thing we struggle with is keeping track of the types of various objects. Some methods expect an Entity whereas others expect plain data objects. Some classes, such as serializers, take either entities or ActiveRecord objects and transform them into plain hashes. Should there be semantic clues that show what each method expects? Or should we use type checking (with https://github.com/plum-umd/rtc for example)?

Jakob Holderbaum

unread,
Feb 13, 2015, 1:48:16 AM2/13/15
to clean-code...@googlegroups.com
Hi Reed,

with growing code bases of untyped languages like Ruby is, this kind of
stuff tends to happen. I discovered it myself a few times.

I would step away from such frameworks for two reasons:

1) You introduce an external dependency (an actual framework) to your
sacred inner code base
2) It kind of runs against the philosophy of untyped languages,
independent of your agreement with it :)

What I tried (and I found it to be OK) is the expression of intent by
calling explicit methods on the received instances. That gives you at
least a hint about the expected interface.

In addition to this we used a lot of Bound
(https://github.com/neopoly/bound) for DTO and Request objects in the
last projects at my former employer. I felt this was a great success.

But it does not help with the problem of missing types when you pass for
example service or entity instances as injected dependencies.

Martin Fowler suggested a simple Haskell like type annotation in a
comment. Here is a simple example:

```
class StoreRequest; ... end
class UID; ... end

class Repository
# :: StoreRequest -> UID
def store(request)
end
end
```

I do like this approach as a very slick form of documentation, though I
never tried it on a code base myself.

Cheers
Jakob
--
Jakob Holderbaum, M.Sc.
Embedded Software Engineer

0176 637 297 71
http://jakob.io/
http://jakob.io/mentoring/
h...@jakob.io
@hldrbm

Caio Fernando Paes de Andrade

unread,
Feb 13, 2015, 5:50:03 AM2/13/15
to clean-code...@googlegroups.com
I would advise against the comments and would prefer to either rename the arguments to be clearer (and have the IDE hint them to me) or rename the method signature to make explicit what kind of argument is needs:

def store(address_dto)
end

def store_address_dto(address)
end

Caio

Sent from my iPhone
--
The only way to go fast is to go well.
---
You received this message because you are subscribed to the Google Groups "Clean Code Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clean-code-discu...@googlegroups.com.
To post to this group, send email to clean-code...@googlegroups.com.
Visit this group at http://groups.google.com/group/clean-code-discussion.

Kris Leech

unread,
Feb 13, 2015, 9:34:11 AM2/13/15
to clean-code...@googlegroups.com
Not sure if this helps but I often check that a given object duck types correctly. I prefer not to check the type as that makes it harder to pass in doubles in the specs.

def call(attributes)
  assert_hash(attributes)
  # ...
end

private

def assert_hash(attributes)
  raise ArgumentError, "given argument does not appear hash-like" unless attributes.respond_to?(:fetch)
end

Side note: I recently wrote a small dependency injection gem, Medicine[1]. Your post got me thinking that I could add an option to validate that a given dependency quacks correctly.

e.g. dependency :my_repo, quacks: [:get, :create]

[1] https://github.com/krisleech/medicine

13 February 2015 03:05
After implementing Clean Architecture in a large Rails project there is a nice separation between the framework and business logic. But one thing we struggle with is keeping track of the types of various objects. Some methods expect an Entity whereas others expect plain data objects. Some classes, such as serializers, take either entities or ActiveRecord objects and transform them into plain hashes. Should there be semantic clues that show what each method expects? Or should we use type checking (with https://github.com/plum-umd/rtc for example)?

Kris Leech

unread,
Feb 13, 2015, 9:35:10 AM2/13/15
to clean-code...@googlegroups.com


13 February 2015 06:47


In addition to this we used a lot of Bound
(https://github.com/neopoly/bound) for DTO and Request objects in the
last projects at my former employer. I felt this was a great success.
This looks similar to the idea of form objects? I'm a big fan of passing form objects instead of plain hashes as you can validate them first.

I usually opt for something like:

class Form
  include Virtus
  include ActiveModel::Naming
  include ActiveModel::Validations

  attribute :first_name, String
  attribute :last_name, String

  validate :first_name, :last_name, presence: true
end


13 February 2015 03:05
After implementing Clean Architecture in a large Rails project there is a nice separation between the framework and business logic. But one thing we struggle with is keeping track of the types of various objects. Some methods expect an Entity whereas others expect plain data objects. Some classes, such as serializers, take either entities or ActiveRecord objects and transform them into plain hashes. Should there be semantic clues that show what each method expects? Or should we use type checking (with https://github.com/plum-umd/rtc for example)?
--

Indrit Selimi

unread,
Feb 19, 2015, 11:10:00 AM2/19/15
to clean-code...@googlegroups.com
But if we have to check if a "duck" is a "duck" what is the added value of "ducks" then?

Reed Law

unread,
Feb 26, 2015, 11:04:29 PM2/26/15
to clean-code...@googlegroups.com, mail...@jakob.io
Hi Jakob,

I like your suggestions, although I'm not sure about the intent behind Bound.

What we've decided to try for now is to work with entities as much as possible until the final step. In a `response` method all the entities can be serialized into DTO. We use builders for creating entities from the data stored in repositories. This way we work with mainly entities by convention and only at the last step get a hash-like object.

Thanks,
Reed

Kris Leech

unread,
Feb 27, 2015, 4:29:55 AM2/27/15
to clean-code...@googlegroups.com


Indrit Selimi wrote:
> But if we have to check if a "duck" is a "duck" what is the added value of "ducks" then?
That it is still a duck, i.e. the type can be anything. All we are
checking is it quacks like a duck. I think in a language like Java this
would be something like an interface.

The difference between checking the duck quacks (assert_hash in the
example) and not doing so is where you get the error. If you expect a
given object to have a `fetch` method and you pass one that does not
then you'll either get an error, with a nice message, where you assert
it responds_to?(:fetch), or somewhere deeper in the stack where you
eventually call `fetch`. Personally I'd prefer the error as early as
possible. Some might call this defensive programming and it is cast in a
negative light but I find it very useful.

I use this technique very lightly on the outer boundaries. If I expect a
Hash-like object I would check for `fetch`, if an enumerable, then `each`.
Reply all
Reply to author
Forward
0 new messages