Vapor 4:如何将急切加载的父关系映射到不同的格式?

Vapor 4: how to map an eagerly loaded parent relation into a different format?

我正在为如何 return 一个包含父关系的模型,同时将急切加载的模型映射到不同的形式而苦苦挣扎。

让我们考虑以下 2 个模型:CourseUser

final class Course: Model, Content {
  static let schema = "courses"

  @ID(key: .id)
  var id: UUID?

  @Field(key: "name")
  var name: String

  @Parent(key: "teacher_id")
  var teacher: User

  init() { }
}

final class User: Model, Content {
  static let schema = "users"

  @ID(key: .id)
  var id: UUID?

  @OptionalField(key: "avatar")
  var avatar: String?

  @Field(key: "name")
  var name: String

  @Field(key: "private")
  var somePrivateField: String

  init() { }
}

我有一条这样的路线,其中 return 是一系列课程:

func list(req: Request) throws -> EventLoopFuture<[Course]> {
  return Course
    .query(on: req.db)
    .all()
}

结果 JSON 看起来像这样:

[
  {
    "id": 1,
    "name": "Course 1",
    "teacher": {
      "id": 1
    }
]

我想要的是教师对象是 returned,通过在查询中添加 .with(\.$teacher) 就很容易了。 Vapor 4 确实让这一切变得非常简单!

[
  {
    "id": 1,
    "name": "Course 1",
    "teacher": {
      "id": 1,
      "name": "User 1",
      "avatar": "https://www.example.com/avatar.jpg",
      "somePrivateField": "super secret internal info"
    }
]

我的问题是:整个 User 对象都是 returned,几乎所有字段,甚至是我不想创建的字段 public.

将教师信息转换为不同版本的 User 模型(例如 PublicUser)的最简单方法是什么?这是否意味着我必须为 Course 创建一个 DTO,将我的数组从 [Course] 映射到 [PublicCourse],复制所有属性,并在 Course 模型更改时使它们保持同步等等?

这似乎是很多样板文件,未来有很大的错误余地。很想听听是否有更好的选择。

您可以通过先对原始模型进行编码,然后将其解码为具有较少字段的结构来实现。因此,要将存储在 course 中的 Course 实例转换为 PublicCourse,您可以执行以下操作:

struct PublicCourse: Decodable {
    //...
    let teacher: PublicUser
    //...
}

let course:Course = // result of Course.query including `with(\.$teacher)`
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let data = try encoder.encode(course)
let publicCourse = try decoder.decode(PublicCourse.self, from: data)

注意结构中的 PublicUser 字段。如果这是精简版,你可以一次性生成你的最小JSON。

好的,这个方法怎么样?创建第二个 Model,称为 Teacher,例如,它被定义为要在 API/JSON 中公开的 User 字段的子集,并且具有相同的 schema/table 名称为 User:

final class Teacher: Model, Content {
  static let schema = "users"

  // public fields
}

然后将 Course 中的关系更改为:

@Parent(key: "teacher_id")
var teacher: Teacher

您的原始查询将保持不变,只是返回减少的字段集。如果您使用 Teacher 只读,它肯定会起作用。无需为 Teacher 创建 Migrations,因为基础 table 存在。我看不到避免包含 ID 的方法,但这可能不是问题。