# Best practices for using Dry-Types, Dry-Schema, Dry-Validation, and Dry-Struct together in our apps

**URL:** <https://discourse.dry-rb.org/t/best-practices-for-using-dry-types-dry-schema-dry-validation-and-dry-struct-together-in-our-apps/1821>\
**Category:** Uncategorized\
**Created:** [August 7, 2024, 2:44am UTC](https://discourse.dry-rb.org/t/best-practices-for-using-dry-types-dry-schema-dry-validation-and-dry-struct-together-in-our-apps/1821 "2024-08-07T02:44:47Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Damian](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/damian/32/963_2.png) [@Damian](https://discourse.dry-rb.org/u/Damian)\
**Post date:** [August 7, 2024, 2:44am UTC](https://discourse.dry-rb.org/t/best-practices-for-using-dry-types-dry-schema-dry-validation-and-dry-struct-together-in-our-apps/1821/1 "2024-08-07T02:44:47Z")

</div>

I am exploring app development with Hanami 2, which has led me to dive into the Dry-rb ecosystem as well. Loving it so far! ❤

I am having some trouble understanding how to coordinate my usage of the various Dry-rb components to avoid logic duplication in my apps. I would love to hear from others how you are using the Dry-rb ecosystem to model domain objects and logic in your apps.

For example, I like the idea of building up a library of Types to represent all data that comes from outside of the system (user-submitted data, imported data, etc.). No primitives allowed! 🤨

However, these types can become quite complex when they represent domain objects with a dozen or more attributes. How do you avoid repeating these attributes and their constraints in multiple places?

For example, I think I can define a Hash Type representing a domain object, and then import the Type into a Dry-Struct definition to avoid having to list all of the attributes in two places (I haven’t really had to do this yet, as I’m still relying on ROM auto-structs). Is this a common practice?

What about schemas? I would also like to avoid repeating all of these attributes in a schema that I could use to parse user input params and other external data (that is, to coerce, transform, and validate the params). But inferring a schema from a Hash Type, while possible, is imperfect and can fail in crucial ways. I can provide a code example for this, but I don’t want to clutter up the initial post.

So . . . what are you all doing?

- How do you organize your Types?
- How do you re-use them to avoid duplicate logic?
- Do you use Types in Schemas and Structs, or is this a pipe dream?
- If you use Hanami, do you define Types for specific slices?
- In short, what are the best practices?

Thank you for reading and for any and all replies! 💚

---

<div class="post-metadata">

**Author:** ![Damian](https://yyz1.discourse-cdn.com/flex031/user_avatar/discourse.dry-rb.org/damian/32/963_2.png) [@Damian](https://discourse.dry-rb.org/u/Damian)\
**Post date:** [August 7, 2024, 3:00am UTC](https://discourse.dry-rb.org/t/best-practices-for-using-dry-types-dry-schema-dry-validation-and-dry-struct-together-in-our-apps/1821/2 "2024-08-07T03:00:19Z")

</div>

This is an example of how Hash Types can fail when used to infer a Schema used to validate params.

Let’s say we have a `Contact` which can be represented by a Hash with an `:id` key (an integer) and a `:name` key (a string). If I wanted to create a Type for this that coerces empty-string values to nil for both keys, it might look like this (please tell me if there is a better way):

```ruby
Contact = Types::Hash.schema(
  id: Params::Integer.optional.fallback(nil),
  name: Params::String.constrained([:filled]).optional.fallback(nil)
)

```

This works, although it actually coerces _all_ invalid values to `nil`. But if I try to use this Type in a Schema, it will fail:

```ruby
contact_schema = Dry::Schema.Params { required(:contact).filled(Types::Contact) }

contact_schema.(contact: {id: "", name: ""}).inspect

=> "#<Dry::Schema::Result{:contact=>{:id=>nil, :name=>nil}} errors={:contact=>{:id=>[\"must be an integer\"], :name=>[\"must be a string\"]}} path=[]>"

```

As you can see, the coercion to `nil` took place, but `nil` is no longer an accepted value.

If I define my own schema as follows, without using the Type, it works just fine:

```ruby
contact_schema = Dry::Schema.Params do
  required(:contact).hash do
    required(:id).maybe(:integer)
    required(:name).maybe(:string)
  end  
end

contact_schema.(contact: {id: "", name: ""}).inspect

=> "#<Dry::Schema::Result{:contact=>{:id=>nil, :name=>nil}} errors={} path=[]>"

```

This is actually better than the Type, because only empty string values are coerced to `nil`. Other invalid values will produce a validation error.

What am I missing? Am I doing something wrong, or is this just not possible? 🧐

Thanks again for reading!
