Dapr HelloWorld

Dapr

分布式应用运行时、事件驱动、为云和边缘构建微服务提供便携化运行时。

我现在也不是很懂。

dapr/dapr

GitHub

比上面的介绍多了 stateless or stateful 的标签。学《计算理论》的时候接触过一些状态机。

注意提到了多语言和多开发者框架,我认为这是他选择的通过通信共享信息,即 HTTPGRPC 支持多语言等特性。微软想通过这个设定一个构建微服务应用的规则。从根本上确立你开发的每一个应用的独立性。

下面进行一个 QuickStart

环境

  1. Install Docker(微服务已经离不开容器化了)
  2. Install Dapr
  3. Node.js version 8 or greater(这个 Helloworld 是 node 应用)

On MacOS

Install the latest darwin Dapr CLI to /usr/local/bin

curl -fsSL https://raw.githubusercontent.com/dapr/cli/master/install/install.sh | /bin/bash

有条件可以加速

执行初始化(会启动 docker 容器)

$ dapr init
⌛  Making the jump to hyperspace...
Downloading binaries and setting up components
✅  Success! Dapr is up and running

$ docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                        NAMES
b3a5600e672f        redis                "docker-entrypoint.s…"   44 hours ago        Up 44 hours         0.0.0.0:6379->6379/tcp       xenodochial_hofstadter
e5010ba0c33f        daprio/dapr          "./placement"            44 hours ago        Up 44 hours         0.0.0.0:50005->50005/tcp     dapr_placement

HelloWorld

Application Architecture

微软的分布式应用框架 Dapr Helloworld-LMLPHP
能够看到暴露两个 endpoint 是 HTTP 访问,一个创建一个查询。

主要看我们使用 Dapr 的交互。在图中它作为 Runtime 

  • 提供 Dapr API 给多语言调用。
  • 提供 状态管理 By state stores

Download Code

下载并进入相应文件夹

git clone https://github.com/dapr/samples.git
cd samples/1.hello-world

Cat app.js

// $ cat app.js
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------

const express = require('express');
const bodyParser = require('body-parser');
require('isomorphic-fetch');

const app = express();
app.use(bodyParser.json());

const daprPort = process.env.DAPR_HTTP_PORT || 3500;
const stateUrl = `http://localhost:${daprPort}/v1.0/state`;
const port = 3000;

app.get('/order', (_req, res) => {
    fetch(`${stateUrl}/order`)
        .then((response) => {
            return response.json();
        }).then((orders) => {
            res.send(orders);
        });
});

app.post('/neworder', (req, res) => {
    const data = req.body.data;
    const orderId = data.orderId;
    console.log("Got a new order! Order ID: " + orderId);

    const state = [{
        key: "order",
        value: data
    }];

    fetch(stateUrl, {
        method: "POST",
        body: JSON.stringify(state),
        headers: {
            "Content-Type": "application/json"
        }
    }).then((response) => {
        console.log((response.ok) ? "Successfully persisted state" : "Failed to persist state");
    });

    res.status(200).send();
});

app.listen(port, () => console.log(`Node App listening on port ${port}!`));

这是一些路由和 handlers

注意 14-16 行

const daprPort = process.env.DAPR_HTTP_PORT || 3500;
const stateUrl = `http://localhost:${daprPort}/v1.0/state`;
const port = 3000;

3500 是 Dapr 的环境端口,如果你安装时有改动,需要考虑。
stateurl 就是 Dapr 提供的 URL 了

Handlers

/neworder
app.post('/neworder', (req, res) => {
    const data = req.body.data;
    const orderId = data.orderId;
    console.log("Got a new order! Order ID: " + orderId);

    const state = [{
        key: "order",
        value: data
    }];

    fetch(stateUrl, {
        method: "POST",
        body: JSON.stringify(state),
        headers: {
            "Content-Type": "application/json"
        }
    }).then((response) => {
        console.log((response.ok) ? "Successfully persisted state" : "Failed to persist state");
    });

    res.status(200).send();
});

这里重点是状态存储,即将 state 通过 stateurl 存储在 Dapr 中。

/order

我们并不是直接通过 res.json 作为 Response 来进行已经持久化的数据的使用,而是通过暴露一个 GET endpoint 通过访问它来验证持久化是否成功。

app.get('/order', (_req, res) => {
    fetch(`${stateUrl}/order`)
        .then((response) => {
            return response.json();
        }).then((orders) => {
            res.send(orders);
        });
});

现在我们通过状态转移在 Dapr 里实现了 stateless,同样我们也可以在加上一个 local cache 并通过一个新的 endpoint 访问来使 Node application 变成 stateful

Dapr Run Node.js App

  1. npm install :通过当前目录下的 package.json , 会安装 express 和 body-parser ,在 app.js 7-8行我们可以看到这两项。
  2. dapr run --app-id mynode --app-port 3000 --port 3500 node app.js
$ dapr run --app-id mynode --app-port 3000 --port 3500 node app.js
ℹ️  Starting Dapr with id mynode. HTTP Port: 3500. gRPC Port: 55099
✅  You're up and running! Both Dapr and your app logs will appear here.

应该是有后台运行的 CLI 命令,这里是前台打印的日志

== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="starting Dapr Runtime -- version 0.1.0 -- commit 4358565-dirty"
== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="log level set to: info"
== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="standalone mode configured"
== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="dapr id: mynode"
== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="loaded component messagebus (pubsub.redis)"
== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="loaded component statestore (state.redis)"
== DAPR == time="2019-11-06T10:37:41+08:00" level=info msg="application protocol: http. waiting on port 3000"
== APP == Node App listening on port 3000!
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="application discovered on port 3000"
== DAPR == 2019/11/06 10:37:42 redis: connecting to localhost:6379
== DAPR == 2019/11/06 10:37:42 redis: connected to localhost:6379 (localAddr: [::1]:55130, remAddr: [::1]:6379)
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actor runtime started. actor idle timeout: 1h0m0s. actor scan interval: 30s"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actors: starting connection attempt to placement service at localhost:50005"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="http server is running on port 3500"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="gRPC server is running on port 55099"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="local service entry announced"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="dapr initialized. Status: Running. Init Elapsed 945.8297490000001ms"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actors: established connection to placement service at localhost:50005"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actors: placement order received: lock"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actors: placement order received: update"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actors: placement tables updated"
== DAPR == time="2019-11-06T10:37:42+08:00" level=info msg="actors: placement order received: unlock"

⚠️:注意到 Node App 在指定的 3000 端口运行,同时还有状态存储的 redis6379 端口运行

Post and Get

接下来注意,文中的端口是 app.js 里默认的 3500

Post

Curl
curl -XPOST -d @sample.json http://localhost:3500/v1.0/invoke/mynode/method/neworder

Vscode

如果你用 vscode ,使用这个插件 Rest Client Plugin
然后打开目录下的 sample.http , 可以看到 send request 的选项
sample.http 

POST http://localhost:3500/v1.0/invoke/mynode/method/neworder
{
  "data": {
    "orderId": "42"
  }
}

Postman

如图: http://localhost:3500/v1.0/invoke/mynode/method/neworder 微软的分布式应用框架 Dapr Helloworld-LMLPHP

Result Update

你可以在你启动的终端中看到新的日志

== APP == Got a new order! Order ID: 42
== APP == Successfully persisted state

Get

Curl
curl http://localhost:3500/v1.0/invoke/mynode/method/order

Vscode

sample.http

GET http://localhost:3500/v1.0/invoke/mynode/method/order

Postman

微软的分布式应用框架 Dapr Helloworld-LMLPHP

Terminate

ctrl + c 或者 dapr stop --app-id mynode

^C
ℹ️  terminated signal received: shutting down
✅  Exited Dapr successfully
✅  Exited App successfully

Feature

  • 具有可插入提供程序和至少一次语义的事件驱动的Pub-Sub系统
  • 使用可插入提供程序的输入和输出绑定
  • 具有可插拔数据存储的状态管理
  • 一致的服务到服务发现和调用
  • 选择加入状态模型:强大/最终一致性,首次写入/最后写入获胜
  • 跨平台虚拟演员
  • 限速
  • 使用OpenTelemetry的内置分布式跟踪
  • 使用专用的Operator和CRD在Kubernetes上本地运行
  • 通过HTTP和gRPC支持所有编程语言
  • 来自Azure,AWS,GCP的多云,开放式组件(绑定,发布-订阅,状态)
  • 作为过程或容器化在任何地方运行
  • 轻量级(58MB二进制,4MB物理内存)
  • 作为辅助工具运行-无需特殊的SDK或库
  • 专用的CLI-易于调试的开发人员友好体验
  • Java,Dotnet,Go,Javascript和Python的客户端

Refer

11-07 04:30