Serverpod 适合全栈 Dart 开发吗?

Serverpod 是一个符合现代化需求的大前端框架,支持 Docker 部署、代码自动生成、数据库连接等功能。对于只会 Dart 语言的开发者来说,Serverpod 是否能胜任全栈开发?本文为您详细评估 Serverpod 的优势和局限性。

Serverpod 适合全栈 Dart 开发吗?

视频

https://youtu.be/RiicBU8fE-o

https://www.bilibili.com/video/BV1nM4m1k7DE/

前言

原文 https://ducafecat.com/blog/full-stack-development-with-dart-and-serverpod

Serverpod 是一个符合现代化需求的大前端框架,支持 Docker 部署、代码自动生成、数据库连接等功能。对于只会 Dart 语言的开发者来说,Serverpod 是否能胜任全栈开发?本文为您详细评估 Serverpod 的优势和局限性。

Serverpod, Dart 全栈开发, Dart 开发框架, 大前端框架, 全栈开发解决方案

Serverpod

https://serverpod.dev/

主要功能有:

  • Docker 生态部署
  • 代码自动生成 model、dao、controller、client sdk
  • 连接 postgres、redis
  • 可配置 yaml , 开发、生产隔离
  • 可视化日志UI工具 insights
  • 缓存、日志、验证、上传文件、健康检查、调度任务、流消息、RPC

如果你只是用来做以上业务,没有问题,但是再复杂些就要靠生态了,如图片加工、视频压缩、经纬度转换、P2P等等,你估计要用 java、go 这种生态丰富的语言来搭建了,然后用 Serverpod 来访问,当然你也可以用 saas 云服务解决。

如果你正在构建一个精简的单体服务架构,你可以从 Serverpod 的设计中寻找灵感。

参考

https://github.com/serverpod/serverpod

https://docs.serverpod.dev/

https://docs.serverpod.dev/insights

https://marketplace.visualstudio.com/items?itemName=serverpod.serverpod

前提条件

步骤

1 全局安装 serverpod 命令行工具

安装

$ dart pub global activate serverpod_cli

自检

$ serverpod
 serverpod 

Manage your serverpod app development

Usage: serverpod <command> [arguments]

Global options:
-h, --help              Print this usage information.
-q, --quiet             Suppress all serverpod cli output. Is overridden by  -v, --verbose.
-v, --verbose           Prints additional information useful for development. Overrides --q, --quiet.
-a, --[no-]analytics    Toggles if analytics data is sent to Serverpod.
                        (defaults to on)

Available commands:
  create                    Creates a new Serverpod project, specify project name (must be lowercase with no special characters).
  create-migration          Creates a migration from the last migration to the current state of the database.
  create-repair-migration   Repairs the database by comparing the target state to what is in the live database instead of comparing to
                            the latest migration.
  generate                  Generate code from yaml files for server and clients.
  language-server           Launches a serverpod language server communicating with JSON-RPC-2 intended to be used with a client
                            integrated in an IDE.
  version                   Prints the active version of the Serverpod CLI.

Run "serverpod help <command>" for more information about a command.

2 创建项目

create 命令

$ serverpod create mypod

mypod 是项目名称

目录

代码名称
mypod_client客户端链接库
mypod_flutterflutter 项目
mypod_serverserverpod 服务端

3 启动项目

Docker 方式启动 postgres、redis

$ cd mypod/mypod_server
$ docker compose up --build --detach

启动 Serverpod 服务

$ dart bin/main.dart --apply-migrations

启动 Flutter 程序

$ cd mypod/mypod_flutter
$ flutter run -d chrome

4 配置文件

开发、生产、测试 配置

mypod_server/config/development.yaml

mypod_server/config/production.yaml

mypod_server/config/staging.yaml

# This is the configuration file for your local development environment. By
# default, it runs a single server on port 8080. To set up your server, you will
# need to add the name of the database you are connecting to and the user name.
# The password for the database is stored in the config/passwords.yaml.
#
# When running your server locally, the server ports are the same as the public
# facing ports.

# Configuration for the main API server.
apiServer:
  port: 8080
  publicHost: localhost
  publicPort: 8080
  publicScheme: http

# Configuration for the Insights server.
insightsServer:
  port: 8081
  publicHost: localhost
  publicPort: 8081
  publicScheme: http

# Configuration for the web server.
webServer:
  port: 8082
  publicHost: localhost
  publicPort: 8082
  publicScheme: http

# This is the database setup for your server.
database:
  host: localhost
  port: 8090
  name: mypod
  user: postgres

# This is the setup for Redis.
redis:
  enabled: false
  host: localhost
  port: 8091

密码配置

mypod_server/config/passwords.yaml

# Use this file to store passwords to services that your server you use. When
# the server starts, the passwords will be automatically loaded and can be
# accessed from the `session.passwords` field. If you don't have access to a
# session object, passwords can also be accessed from
# `Serverpod.instance.passwords`. You can provide different passwords for
# different run configurations. If you want the same password for any
# configuration used, place it under `shared`.
#
# Note that this file should not be under version control. Store it in a safe
# place.

# Save passwords used across all configurations here.
shared:
  mySharedPassword: 'my password'

# These are passwords used when running the server locally in development mode
development:
  database: 'jBXWslvjYpfszBbFKS661o1MrY1esjVC'
  redis: 'ijH4ON9CCHtK9Cojnqivvym1zxHkwjig'

  # The service secret is used to communicate between servers and to access the
  # service protocol.
  serviceSecret: 'uVoVJ-cqnwc9Bn8gQG-ZLtYfgXGnMUu3'

# Passwords used in your staging environment if you use one. The default setup
# use a password for Redis.
staging:
  database: '3_5RPi7yVOSwSEhr8ecNftp1K4H0_7nL'
  serviceSecret: 'JgM8DPORU5OwOyG23_gdjhYuRLD6mk1J'

# Passwords used in production mode.
production:
  database: 'ReE97mCuS0QaxlXEy4hNMQYtE_Q1lxP4'
  serviceSecret: '0XpMqP4aybryFxm4N8V5ZXHcPFTHUXpP'

生成配置

type: server

client_package_path: ../mypod_client

docker 服务配置

mypod_server/docker-compose.yaml

version: '3.7'

services:
  postgres:
    image: postgres:14.1
    ports:
      - '8090:5432'
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: mypod
      POSTGRES_PASSWORD: "jBXWslvjYpfszBbFKS661o1MrY1esjVC"
    volumes:
      - mypod_data:/var/lib/postgresql/data
  redis:
    image: redis:6.2.6
    ports:
      - '8091:6379'
    command: redis-server --requirepass "ijH4ON9CCHtK9Cojnqivvym1zxHkwjig"
    environment:
      - REDIS_REPLICATION_MODE=master
volumes:
  mypod_data:

5 动态生成接口代码

添加 example.hello2 方法

mypod_server/lib/src/endpoints/example_endpoint.dart

class ExampleEndpoint extends Endpoint {
  // You create methods in your endpoint which are accessible from the client by
  // creating a public method with `Session` as its first parameter.
  // `bool`, `int`, `double`, `String`, `UuidValue`, `Duration`, `DateTime`, `ByteData`,
  // and other serializable classes, exceptions and enums from your from your `protocol` directory.
  // The methods should return a typed future; the same types as for the parameters are
  // supported. The `session` object provides access to the database, logging,
  // passwords, and information about the request being made to the server.
  Future<String> hello(Session session, String name) async {
    return 'Hello $name';
  }

  Future<String> hello2(Session session, String name) async {
    return 'Hello $name';
  }
}

执行生成命令

$ cd mypod/mypod_server
$ serverpod generate

查看 generated 目录的文件变化

mypod_server/lib/src/generated

客户端 lib 库

mypod_client/lib/src/protocol/client.dart

class EndpointExample extends _i1.EndpointRef {
  EndpointExample(_i1.EndpointCaller caller) : super(caller);

  @override
  String get name => 'example';

  _i2.Future<String> hello(String name) => caller.callServerEndpoint<String>(
        'example',
        'hello',
        {'name': name},
      );

  _i2.Future<String> hello2(String name) => caller.callServerEndpoint<String>(
        'example',
        'hello2',
        {'name': name},
      );
}

多了 hello2 的定义

Flutter 项目中调用

mypod_flutter/lib/main.dart

  void _callHello2() async {
    try {
      final result = await client.example.hello2(_textEditingController.text);
      setState(() {
        _errorMessage = null;
        _resultMessage = result;
      });
    } catch (e) {
      setState(() {
        _errorMessage = '$e';
      });
    }
  }
...
  
  Padding(
              padding: const EdgeInsets.only(bottom: 16.0),
              child: ElevatedButton(
                onPressed: _callHello2,
                child: const Text('Send to Server'),
              ),
            ),

6 动态生成数据库代码

编写 model 定义

mypod_server/lib/src/models/note.yaml

class: Note
table: note
fields:
  text: String

生成代码

serverpod generate

编写 CURD 接口定义

mypod_server/lib/src/endpoints/notes_endpoint.dart

import 'package:serverpod/server.dart';

import '../generated/protocol.dart';

class NotesEndpoint extends Endpoint {
  Future<List<Note>> getAllNotes(Session session) async {
    // By ordering by the id column, we always get the notes in the same order
    // and not in the order they were updated.
    return await Note.db.find(
      session,
      orderBy: (t) => t.id,
    );
  }

  Future<void> createNote(Session session, Note note) async {
    await Note.db.insertRow(session, note);
  }

  Future<void> deleteNote(Session session, Note note) async {
    await Note.db.deleteRow(session, note);
  }
}

生成迁移内容

$ serverpod create-migration

服务器启动应用迁移

$ cd mypod/mypod_server
$ dart bin/main.dart --apply-migrations

重启服务

迁移完成后需要重新启动服务器

dart bin/main.dart --apply-migrations

Flutter 编写代码

mypod_flutter/lib/main.dart

  void _callNoteCreate() async {
    try {
      await client.notes.createNote(Note(text: "this is example."));
      setState(() {
        _errorMessage = null;
        _resultMessage = "create send~";
      });
    } catch (e) {
      setState(() {
        _errorMessage = '$e';
      });
    }
  }
Padding(
              padding: const EdgeInsets.only(bottom: 16.0),
              child: ElevatedButton(
                onPressed: _callNoteCreate,
                child: const Text('Note Create Send To Server'),
              ),
            ),

Flutter 项目访问测试

$ cd mypod/mypod_flutter
$ flutter run -d chrome

7 部署项目

Docker 部署

mypod_server/Dockerfile

FROM dart:3.2.5 AS build

WORKDIR /app
COPY . .

RUN dart pub get
RUN dart compile exe bin/main.dart -o bin/server

FROM alpine:latest

ENV runmode=production
ENV serverid=default
ENV logging=normal
ENV role=monolith

COPY --from=build /runtime/ /
COPY --from=build /app/bin/server server
COPY --from=build /app/config/ config/
COPY --from=build /app/web/ web/

EXPOSE 8080
EXPOSE 8081
EXPOSE 8082

ENTRYPOINT ./server --mode=$runmode --server-id=$serverid --logging=$logging --role=$role

构建 docker 包

$ cd mypod/mypod_server
$ docker build -t mypod_server:v1.0 .

原生部署

mypod_server/deploy

  • aws
  • gcp

代码

官方有些不错的例子

https://docs.serverpod.dev/tutorials/code-example

小结

Serverpod 是一个功能强大的 Dart 全栈开发框架,为只掌握 Dart 语言的开发者提供了一个全面的解决方案。它支持 Docker 部署、代码自动生成、数据库连接等关键功能,满足了现代化应用开发的需求。但是对于更加复杂的业务场景,开发者可能还需要使用其他语言的生态工具。总的来说,Serverpod 是一个值得 Dart 开发者认真考虑的全栈开发框架,能够大幅提高开发效率和部署效果。

感谢阅读本文

如果有什么建议,请在评论中让我知道。我很乐意改进。


flutter 学习路径


© 猫哥 ducafecat.com

end