首页 iOS.& Swift Books 服务器端迅速与蒸气

37
微服务,第2部分 由Tim Condon撰写

在上一章中,您学习了微服务的基础知识以及如何将架构应用于TIL应用程序。在本章中,您将了解API网关以及如何使客户端访问微服务。最后,您将学习如何使用Docker和Docker Compose旋转整个应用程序。

API网关

前一章介绍了TIL应用程序的两个微服务,一个用于缩略语,一个用于用户。在实际应用程序中,您可能对应用程序的所有不同方面都有更多的服务。客户难以与由如此大量的微服务组成的应用程序集成。每个客户端都需要知道每个微服务和每个服务的URL。客户端甚至可能必须为每个服务使用不同的身份验证方法。微服务架构使得难以将服务拆分为单独的服务。例如,在TIL应用程序中的用户服务中移动身份验证需要更新到所有客户端。

此问题的一个解决方案是API网关。 API网关可以聚合来自客户端的请求并将其分发到所有必需的服务。另外,API网关可以从多个服务中检索结果,并将它们组合成单个响应。

大多数云提供商提供API网关解决方案来管理大量的微服务,但您可以轻松创建自己的。在本章中,你会这样做。

下载本章的Starter项目。这 倾斜者Tilappacronyms. 项目与上一章的最终项目相同。有一个新的 Tilappapi. 包含API网关的骨架的项目。

开始服务

在终端中,打开三个单独的选项卡。确保MySQL,PostgreSQL和Redis Docker容器正在从上一章运行。在终端中,键入以下内容:

docker ps

swift run
swift run
open Package.swift

转发请求

在里面 Tilappapi. Xcode项目,开放 Userscontroller.swift.. Below boot(routes:) enter the following:

// 1
func getAllHandler(_ req: Request) 
  -> EventLoopFuture<ClientResponse> {
    return req.client.get("\(userServiceURL)/users")
}

// 2
func getHandler(_ req: Request) throws 
  -> EventLoopFuture<ClientResponse> {
    let id = try req.parameters.require("userID", as: UUID.self)
    return req.client.get("\(userServiceURL)/users/\(id)")
}

// 3
func createHandler(_ req: Request) 
  -> EventLoopFuture<ClientResponse> {
    return req.client.post("\(userServiceURL)/users") {
      createRequest in
      // 4
      try createRequest.content.encode(
        req.content.decode(CreateUserData.self))
  }
}
// 1
routeGroup.get(use: getAllHandler)
// 2
routeGroup.get(":userID", use: getHandler)
// 3
routeGroup.post(use: createHandler)
// 1
func getAllHandler(_ req: Request) 
  -> EventLoopFuture<ClientResponse> {
    return req.client.get("\(acronymsServiceURL)/")
}

// 2
func getHandler(_ req: Request) throws 
  -> EventLoopFuture<ClientResponse> {
    let id = 
      try req.parameters.require("acronymID", as: UUID.self)
    return req.client.get("\(acronymsServiceURL)/\(id)")
}
// 1
acronymsGroup.get(use: getAllHandler)
// 2
acronymsGroup.get(":acronymID", use: getHandler)

API身份验证

在登录

API Gateway的身份验证以与微猎狼人的方式完全相同。首先,您必须允许用户登录。

func loginHandler(_ req: Request) 
  -> EventLoopFuture<ClientResponse> {
    // 1
    return req.client.post("\(userServiceURL)/auth/login") {
      loginRequest in
        // 2
        guard let authHeader =
          req.headers[.authorization].first else {
            throw Abort(.unauthorized)
        }
        // 3
        loginRequest.headers.add(
          name: .authorization,
          value: authHeader)
    }
}
routeGroup.post("login", use: loginHandler)

访问受保护的路由

回到Xcode,打开 亚焦焦控制器.swift.. Below getHandler(_:), create a new route handler to create an acronym:

func createHandler(_ req: Request) 
  -> EventLoopFuture<ClientResponse> {
    // 1
    return req.client.post("\(acronymsServiceURL)/") {
      createRequest in
        // 2
        guard let authHeader =
          req.headers[.authorization].first else {
            throw Abort(.unauthorized)
        }
        // 3
        createRequest.headers.add(
          name: .authorization,
          value: authHeader)
        // 4
        try createRequest.content.encode(
          req.content.decode(CreateAcronymData.self))
    }
}
acronymsGroup.post(use: createHandler)

func updateHandler(_ req: Request) throws 
  -> EventLoopFuture<ClientResponse> {
    // 1
    let acronymID = 
      try req.parameters.require("acronymID", as: UUID.self)
    // 2
    return req.client
      .put("\(acronymsServiceURL)/\(acronymID)") {
        updateRequest in
          // 3
          guard let authHeader =
            req.headers[.authorization].first else {
              throw Abort(.unauthorized)
          }
          // 4
          updateRequest.headers.add(
            name: .authorization,
            value: authHeader)
          // 5
          try updateRequest.content.encode(
            req.content.decode(CreateAcronymData.self))
    }
}

func deleteHandler(_ req: Request) throws 
  -> EventLoopFuture<ClientResponse> {
    // 6
    let acronymID = 
      try req.parameters.require("acronymID", as: UUID.self)
    // 7
    return req.client
      .delete("\(acronymsServiceURL)/\(acronymID)") {
        deleteRequest in
          // 8
          guard let authHeader =
            req.headers[.authorization].first else {
              throw Abort(.unauthorized)
          }
          // 9
          deleteRequest.headers.add(
            name: .authorization,
            value: authHeader)
    }
}
// 1
acronymsGroup.put(":acronymID", use: updateHandler)
// 2
acronymsGroup.delete(":acronymID", use: deleteHandler)

处理关系

在上一章中,您看到了关系如何与微服务一起使用。在微服务体系结构中,客户对不同型号的关系很难。您可以使用API​​ Gateway来帮助简化此功能。

获取用户的首字母缩略词

在Xcode,打开 Userscontroller.swift.. Below loginHandler(_:), add a new route handler to get a user’s acronyms:

func getAcronyms(_ req: Request) throws 
  -> EventLoopFuture<ClientResponse> {
    // 1
    let userID = 
      try req.parameters.require("userID", as: UUID.self)
    // 2
    return req.client
      .get("\(acronymsServiceURL)/user/\(userID)")
}
routeGroup.get(":userID", "acronyms", use: getAcronyms)

获得首字母缩写的用户

获取用户的首字母缩略词在客户端知道用户的ID时看起来与MicroService中的其他请求相同。获取用户特定的缩写是更复杂的。打开 亚焦焦控制器.swift. 和 add a new route handler to do this below deleteHandler(_:):

func getUserHandler(_ req: Request) throws 
  -> EventLoopFuture<ClientResponse> {
    // 1
    let acronymID = 
      try req.parameters.require("acronymID", as: UUID.self)
    // 2
    return req
      .client
      .get("\(acronymsServiceURL)/\(acronymID)")
      .flatMapThrowing { response in
        // 3
        return try response.content.decode(Acronym.self)
      // 4
      }.flatMap { acronym in
        // 5
        return req
          .client
          .get("\(userServiceURL)/users/\(acronym.userID)")
    }
}
acronymsGroup.get(":acronymID", "user", use: getUserHandler)

在Docker中运行所有内容

您现在有三个组成TIL应用程序的微服务。这些微服务还需要另外三个数据库来工作。如果您正在开发客户端应用程序,或者另一个微服务,则有很多才能运行到开始。您可能还想在Linux中运行所有内容以检查您的服务是否正确部署。就像第11章“测试”一样,您将使用Docker Compose来运行一切。

注入服务URL

目前,应用程序硬编码不同的微服务的URL localhost.。您必须更改此操作以在Docker Compose中运行它们。回到Xcode中 Tilappapi., 打开 亚焦焦控制器.swift.. Replace the definitions of userServiceURLacronymsServiceURL with the following:

let acronymsServiceURL: String
let userServiceURL: String

init(
  acronymsServiceHostname: String,
  userServiceHostname: String) {
    acronymsServiceURL =
      "http://\(acronymsServiceHostname):8082"
    userServiceURL = "http://\(userServiceHostname):8081"
}
let userServiceURL: String
let acronymsServiceURL: String

init(
  userServiceHostname: String,
  acronymsServiceHostname: String) {
    userServiceURL = "http://\(userServiceHostname):8081"
    acronymsServiceURL =
      "http://\(acronymsServiceHostname):8082"
}
let usersHostname: String
let acronymsHostname: String

// 1
if let users = Environment.get("USERS_HOSTNAME") {
  usersHostname = users
} else {
  usersHostname = "localhost."
}

// 2
if let acronyms = Environment.get("ACRONYMS_HOSTNAME") {
  acronymsHostname = acronyms
} else {
  acronymsHostname = "localhost."
}

// 3
try app.register(collection: UsersController(
  userServiceHostname: usersHostname,
  acronymsServiceHostname: acronymsHostname))
try app.register(collection: AcronymsController(
  acronymsServiceHostname: acronymsHostname,
  userServiceHostname: usersHostname))
let authHostname: String

init(authHostname: String) {
  self.authHostname = authHostname
}
"http://\(authHostname):8081/auth/authenticate"
let authHostname: String
// 1
if let host = Environment.get("AUTH_HOSTNAME") {
  authHostname = host
} else {
  authHostname = "localhost."
}
// 2
let authGroup = routes.grouped(
  UserAuthMiddleware(authHostname: authHostname))

Docker撰写文件

在包含所有三个项目的根目录中,创建一个名为的新文件 docker-compose.yml. 并在您选择的编辑中打开它。添加以下内容以定义版本和数据库服务:

# 1
version: '3'
services:
  # 2
  postgres:
    image: "postgres"
    environment:
      - POSTGRES_DB=vapor_database
      - POSTGRES_USER=vapor_username
      - POSTGRES_PASSWORD=vapor_password
  # 3
  mysql:
    image: "mysql"
    environment:
      - MYSQL_USER=vapor_username
      - MYSQL_PASSWORD=vapor_password
      - MYSQL_DATABASE=vapor_database
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
  # 4
  redis:
    image: "redis"
  # 1
  til-users:
    # 2
    depends_on:
      - postgres
      - redis
    # 3
    build:
      context: ./TILAppUsers
      dockerfile: Dockerfile
    # 4
    environment:
      - DATABASE_HOST=postgres
      - REDIS_HOSTNAME=redis
      - PORT=8081
      - ENVIRONMENT=production
  # 1
  til-acronyms:
    # 2
    depends_on:
      - mysql
      - til-users
    # 3
    build:
      context: ./TILAppAcronyms
      dockerfile: Dockerfile
    # 4
    environment:
      - DATABASE_HOST=mysql
      - PORT=8082
      - ENVIRONMENT=production
      - AUTH_HOSTNAME=til-users
  # 1
  til-api:
    # 2
    depends_on:
      - til-users
      - til-acronyms
    # 3
    ports:
      - "8080:8080"
    # 4
    build:
      context: ./TILAppAPI
      dockerfile: Dockerfile
    # 5
    environment:
      - USERS_HOSTNAME=til-users
      - ACRONYMS_HOSTNAME=til-acronyms
      - PORT=8080
      - ENVIRONMENT=production

修改Dockerfiles.

在您可以运行所有内容之前,必须更改Dockerfiles。 Docker Compose以所请求的顺序启动不同的容器,但不会等待它们准备接受连接。如果您的蒸发应用程序尝试在数据库准备好之前连接到数据库,则会导致问题。在 Tilappacronyms., 打开 dockerfile. 和 replace:

ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
ENTRYPOINT sleep 20 && \
  ./Run serve --env $ENVIRONMENT --hostname 0.0.0.0 --port $PORT

运行一切

You’re now ready to spin up your application in Docker Compose. In Terminal, in the directory containing docker-compose.yml.,输入以下内容:

docker-compose up

然后去哪儿?

在本章中,您学习了如何使用蒸气创建API网关。这使客户端简单地与您的不同微服务器进行交互。您学习了如何在不同的微服务之间发送请求并返回单个响应。您还学习了如何使用Docker撰写以构建和启动所有微服务并将它们链接在一起。

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里.

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。