Микросервисы на Swift: архитектура и практика для iOS/macOS

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему Swift для микросервисов?

Традиционно Swift ассоциируется с разработкой под iOS и macOS, однако с появлением серверных фреймворков, таких как Vapor и Kitura, язык стал мощным инструментом для создания микросервисной архитектуры. Микросервисы на Swift позволяют использовать единый язык как на клиенте, так и на сервере, что упрощает поддержку кодовой базы и ускоряет разработку. В этой статье мы разберем ключевые принципы построения микросервисов на Swift, рассмотрим практические примеры с Vapor, Combine и gRPC, а также обсудим вопросы развертывания.

Основная часть

1. Архитектура микросервисов на Swift: базовые принципы

Микросервисная архитектура предполагает разбиение приложения на небольшие, слабо связанные сервисы. Каждый сервис отвечает за свою бизнес-логику и может быть разработан, развернут и масштабирован независимо. Для Swift это означает:

  • Разделение по доменам (DDD): каждый микросервис представляет отдельный bounded context.
  • Независимые базы данных: каждый сервис использует свою БД (PostgreSQL, SQLite, Redis).
  • Событийно-ориентированная коммуникация: через очереди (RabbitMQ, Kafka) или HTTP/gRPC.

Пример базовой структуры проекта на Vapor:

// Структура папок микросервиса "UserService"
UserService/
├── Sources/
│   ├── App/
│   │   ├── Controllers/
│   │   ├── Models/
│   │   ├── Migrations/
│   │   └── configure.swift
├── Tests/
├── Package.swift
└── Dockerfile

Каждый микросервис должен иметь свой Package.swift и точку входа main.swift.

2. Создание микросервиса на Vapor с REST API

Vapor — самый популярный серверный фреймворк для Swift. Рассмотрим создание простого микросервиса для управления пользователями.

2.1 Настройка проекта

Установите Vapor Toolbox и создайте новый проект:

brew install vapor
vapor new UserService --template=api

2.2 Модель и контроллер

Определим модель User с Fluent ORM:

import Fluent
import Vapor

final class User: Model, Content {
    static let schema = "users"
    
    @ID(key: .id)
    var id: UUID?
    
    @Field(key: "name")
    var name: String
    
    @Field(key: "email")
    var email: String
    
    init() { }
    
    init(id: UUID? = nil, name: String, email: String) {
        self.id = id
        self.name = name
        self.email = email
    }
}

Создадим контроллер с CRUD-операциями:

struct UserController: RouteCollection {
    func boot(routes: RoutesBuilder) throws {
        let users = routes.grouped("users")
        users.get(use: index)
        users.post(use: create)
        users.group(":userID") { user in
            user.get(use: show)
            user.put(use: update)
            user.delete(use: delete)
        }
    }
    
    // GET /users
    func index(req: Request) async throws -> [User] {
        try await User.query(on: req.db).all()
    }
    
    // POST /users
    func create(req: Request) async throws -> User {
        let user = try req.content.decode(User.self)
        try await user.save(on: req.db)
        return user
    }
    
    // GET /users/:id
    func show(req: Request) async throws -> User {
        guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
            throw Abort(.notFound)
        }
        return user
    }
    
    // PUT /users/:id
    func update(req: Request) async throws -> User {
        guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
            throw Abort(.notFound)
        }
        let updated = try req.content.decode(User.self)
        user.name = updated.name
        user.email = updated.email
        try await user.save(on: req.db)
        return user
    }
    
    // DELETE /users/:id
    func delete(req: Request) async throws -> HTTPStatus {
        guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
            throw Abort(.notFound)
        }
        try await user.delete(on: req.db)
        return .noContent
    }
}

2.3 Регистрация маршрутов

В файле configure.swift:

public func configure(_ app: Application) throws {
    app.databases.use(.postgres(hostname: "localhost", 
                                username: "vapor", 
                                password: "password", 
                                database: "users_db"), as: .psql)
    app.migrations.add(CreateUser())
    try app.autoMigrate().wait()
    
    try app.register(collection: UserController())
}

3. Межсервисное взаимодействие: gRPC и Combine

Для связи между микросервисами в Swift часто используют gRPC благодаря его производительности и строгой типизации. Рассмотрим пример с использованием grpc-swift.

3.1 Определение proto-файла

syntax = "proto3";

service UserService {
    rpc GetUser (UserRequest) returns (UserResponse);
}

message UserRequest {
    string user_id = 1;
}

message UserResponse {
    string id = 1;
    string name = 2;
    string email = 3;
}

3.2 Генерация Swift-кода

Добавьте плагин в Package.swift:

.package(url: "https://github.com/grpc/grpc-swift.git", from: "1.7.0")

Запустите генерацию:

protoc --swift_out=. --grpc-swift_out=. user.proto

3.3 Реализация сервера и клиента

Серверная часть на Vapor:

import GRPC
import NIO

func startGRPCServer() {
    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
    defer { try! group.syncShutdownGracefully() }
    
    let provider = UserServiceProvider()
    let server = Server.insecure(group: group)
        .withServiceProviders([provider])
        .bind(host: "0.0.0.0", port: 50051)
    
    try! server.onClose.wait()
}

Клиентская часть (например, в другом микросервисе):

import GRPC
import NIO

func getUser(userID: String)                

Блоги

Рекомендаций по книгам