hiccLoghicc log by wccHipo Log

使用Dart开发你的serverless 后端

toc

Why ? 10M的Dart server镜像🤓

这是篇快文。😄

serverless时代,用什么开发你的后端?Go? NodeJs ? 其实随着Flutter的流行,我们还有个选项 -- Dart。

有什么优势让我们选用这个相对小众的语言?

还是它的核心优势,JIT + AOT

10M的镜像尺寸

  • JIT: 极佳的开发体验,特别是前端工程师,像Nodejs一样,甚至有类似express的server框架,而且Dart是强类型语言,最近也做到了null safe 更写起来跟省心,潜在bug少。
  • AOT:极小的构建镜像,10M的尺寸加上执行更快的编译后机器码,保证最高效率的使用云资源。(最小的node镜像也有50M了)

快速开始

创建项目

dart create -t server-simple hipo

入口代码

import 'dart:io';

import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_static/shelf_static.dart' as shelf_static;
import 'package:dotenv/dotenv.dart' show load, clean, isEveryDefined, env;

import 'src/common/db.dart';
import 'src/routers/user.router.dart';

Future main() async {
  load();

  final port = int.parse(Platform.environment['PORT'] ?? '8080');
  final isDev = env['DART_ENV'] == 'production';
  final conf = {
    'isDev': isDev,
    'database': {
      'host': env['DB_HOST'] ?? '127.0.0.1',
    }
  };

  final cascade = Cascade().add(_staticHandler).add(userRouter);

  // init db
  final db = getDb(conf);

  final pipeline = Pipeline()
      .addMiddleware(logRequests())
      .addMiddleware((innerHandler) => (req) async {
            final _req = req.change(context: {'db': db});
            return innerHandler(_req);
          })
      .addHandler(cascade.handler);

  final server = await shelf_io.serve(
      pipeline,
      InternetAddress.anyIPv4, // Allows external connections
      port);

  print('Serving at http://${server.address.host}:${server.port}');
}

final _staticHandler =
    shelf_static.createStaticHandler('public', defaultDocument: 'index.html');

shelf 系列是Google 提供类似前端express中间件形式的开发库,用起来很好上手

路由代码

import 'package:hipo/mysql_utils.dart';
import 'package:shelf_router/shelf_router.dart' as shelf_router;
import 'package:shelf/shelf.dart';
import 'dart:async';

final userRouter = shelf_router.Router()
  ..get('/api/helloworld', _helloWorldHandler);

Future<Response> _helloWorldHandler(Request request) async {
  var data = await (request.context['db'] as MysqlUtils).getAll(table: 'user');
  print('$data');
  return Response.ok({'len': data.length}.toString());
}

mysql 数据库

可以使用mysql1mysql_utils 这两个库。

import 'package:hipo/mysql_utils.dart';
import 'package:mysql1/mysql1.dart';

final getDb = (Map<String, dynamic> conf) => MysqlUtils(
    settings: ConnectionSettings(
      host: conf['database']['host'],
      port: conf['database']['port'],
      user: conf['database']['user'],
      password: conf['database']['password'],
      db: conf['database']['db'],
      useCompression: false,
      useSSL: false,
      // maxPacketSize: 1024 * 1024,
      // characterSet: CharacterSet.UTF8,
      // timeout: const Duration(seconds: 10),
    ),
    // prefix: 'prefix_',
    pool: false,
    errorLog: (error) {
      print('db error |$error\n├───────────────────────────');
    },
    sqlLog: (sql) {
      print('db sql |$sql\n├───────────────────────────');
    },
    connectInit: (db1) async {
      print('whenComplete');
    });

注意:

  • 貌似不支持mysql 8.0+的版本
  • mysql_utils需要变通下使用,作者写明了依赖flutter,实际上server也能用。

Dockerfile

# Official Dart image: https://hub.docker.com/_/dart
# Specify the Dart SDK base image version using dart:<version> (ex: dart:2.12)
FROM dart:stable AS build

# Resolve app dependencies.
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

# Copy app source code and AOT compile it.
COPY . .
# Ensure packages are still up-to-date if anything has changed
RUN dart pub get --offline
RUN dart compile exe bin/hipo.dart -o bin/hipo

# Build minimal serving image from AOT-compiled `/hipo` and required system
# libraries and configuration files stored in `/runtime/` from the build stage.
FROM scratch

ARG DART_ENV=production
ENV DART_ENV=${DART_ENV}

COPY --from=build /runtime/ /
COPY --from=build /app/bin/hipo /app/bin/

# Include files in the /public directory to enable static asset handling
COPY --from=build /app/public/ /public

# Start server.
EXPOSE 8080
CMD ["/app/bin/hipo"]

哇啦~~