Skip to content

Commit

Permalink
Add note about DTO for parent relationship (#965)
Browse files Browse the repository at this point in the history
<!-- πŸš€ Thank you for contributing! -->

<!-- Describe your changes clearly and use examples if possible. -->

<!-- When this PR is merged, the title and body will be -->
<!-- used to generate a release automatically. -->
  • Loading branch information
0xTim committed Feb 9, 2024
1 parent 288b3ac commit 5170deb
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 5 deletions.
12 changes: 7 additions & 5 deletions docs/fluent/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,14 @@ app.get("planets") { req async throws in

When serializing to / from `Codable`, model properties will use their variable names instead of keys. Relations will serialize as nested structures and any eager loaded data will be included.

!!! info
We recommend that for almost all cases you use a DTO instead of a model for your API responses and request bodies. See [Data Transfer Object](#data-transfer-object) for more information.

### Data Transfer Object

Model's default `Codable` conformance can make simple usage and prototyping easier. However, it is not suitable for every use case. For certain situations you will need to use a data transfer object (DTO).
Model's default `Codable` conformance can make simple usage and prototyping easier. However, it exposes the underlying database information to the API. This is usually not desirable from both a security standpoint - returning sensitive fields such as a user's password hash is a bad idea - and a usability point of view. It makes it difficult to change the database schema without breaking the API, accept or return data in a different format, or to add or remove fields from the API.

!!! tip
A DTO is a separate `Codable` type representing the data structure you would like to encode or decode.
For most cases you use use a DTO, or data transfer object instead of a model (this is also known as a domain transfer object). A DTO is a separate `Codable` type representing the data structure you would like to encode or decode. These decouple your API from your database schema and allow you to make changes to your models without breaking your app's public API, have different versions and make your API nicer to use for your clients.

Assume the following `User` model in the upcoming examples.

Expand Down Expand Up @@ -434,9 +436,9 @@ app.get("users") { req async throws -> [GetUser] in
}
```

Even if the DTO's structure is identical to model's `Codable` conformance, having it as a separate type can help keep large projects tidy. If you ever need to make a change to your models properties, you don't have to worry about breaking your app's public API. You may also consider putting your DTOs in a separate package that can be shared with consumers of your API.
Another common use case is when dealing with relations, such as parent relations or children relations. See [the Parent documentation](relations.md##encoding-and-decoding-of-parents) for an example of how to use a DTO to make it easy to decode a model with a `@Parent` relation.

For these reasons, we highly recommend using DTOs wherever possible, especially for large projects.
Even if the DTO's structure is identical to model's `Codable` conformance, having it as a separate type can help keep large projects tidy. If you ever need to make a change to your models properties, you don't have to worry about breaking your app's public API. You may also consider putting your DTOs in a separate package that can be shared with consumers of your API and adding `Content` conformance in your Vapor app.

## Alias

Expand Down
33 changes: 33 additions & 0 deletions docs/fluent/relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,39 @@ The field definition is similar to `@Parent`'s except that the `.required` const
.field("star_id", .uuid, .references("star", "id"))
```

### Encoding and Decoding of Parents

One thing to watch out for when working with `@Parent` relations is the way that you send and receive them. For example, in JSON, a `@Parent` for a `Planet` model might look like this:

```json
{
"id": "A616B398-A963-4EC7-9D1D-B1AA8A6F1107",
"star": {
"id": "A1B2C3D4-1234-5678-90AB-CDEF12345678"
}
}
```

Note how the `star` property is an object rather than the ID that you might expect. When sending the model as an HTTP body, it needs to match this for decoding to work. For this reason, we strongly recommend using a DTO to represent the model when sending it over the network. For example:

```swift
struct PlanetDTO: Content {
var id: UUID?
var name: String
var star: Star.IDValue
}
```

Then you can decode the DTO and convert it into a model:

```swift
let planetData = try req.content.decode(PlanetDTO.self)
let planet = Planet(id: planetData.id, name: planetData.name, starID: planetData.star)
try await planet.create(on: req.db)
```

The same applies when returning the model to clients. Your clients either need to be able to handle the nested structure, or you need to convert the model into a DTO before returning it. For more information about DTOs, see the [Model documentation](model.md#data-transfer-object)

## Optional Child

The `@OptionalChild` property creates a one-to-one relation between the two models. It does not store any values on the root model.
Expand Down

0 comments on commit 5170deb

Please sign in to comment.