dapr/php-sdk

PHP中的Dapr实现

v1.2.0 2021-12-19 12:49 UTC

README

PHP Composer

此库采用MIT许可证。

将库添加到您的composer.json

composer require dapr/php-sdk

以下是一些基本文档,更多文档可以在文档中找到

迁移到1.2

为了准备在此SDK中支持gRPC,现在在\Dapr\Client\DaprClient中有一个新的DaprClient。请更新您的代码以使用新客户端。

从先前的版本升级到1.2+时,您的代码可能不会有很多变化。特别是,有一些弃用

弃用

以下已被弃用,将在1.4+中删除。

\Dapr\SecretManager已被弃用

只需使用新客户端即可。

\Dapr\Client已被弃用

只需使用新客户端:\Dapr\Client\DaprClient

\Dapr\PubSub\Publish已被弃用

只需直接实例化\Dapr\PubSub\Topic或直接使用新客户端。

回退和升级

\Dapr\State\StateManager

此类已升级以使用新客户端。它不应需要您对代码进行任何更改,但是,您可以使用\Dapr\State\StateManagerOld利用旧行为。

\Dapr\State\TransactionalState

此类已升级以使用新客户端。它不应需要您对代码进行任何更改,但是,您可以使用\Dapr\State\TransactionalStateOld利用旧行为。

创建Dapr客户端

$client = \Dapr\Client\DaprClient::clientBuilder()->build();

使用中间件

App对象还实现了PSR-15兼容的中间件,该中间件为您实现了actor路由和订阅路由。

$app = \Dapr\App::create(configure: [
    // custom configuration
]);
use_middleware($app);

访问密钥

您可以轻松访问密钥

<?php

// retrieve a single secret
$client->getSecret(storeName: 'kubernetes', key: 'test');

// retrieve all secrets
$client->getBulkSecret(storeName: 'kubernetes');

访问状态

有几种访问状态的方法。您可以直接通过客户端访问状态,或通过对象进行抽象访问。

直接通过客户端访问

['value' => $value, 'etag' => $etag] = $client->getStateAndEtag(storeName: 'statestore', key: 'key', asType: SomeClass::class, consistency: \Dapr\consistency\EventualLastWrite::instance());
$value = $client->getState(storeName: 'statestore', key: 'key', asType: 'string',consistency: \Dapr\consistency\StrongFirstWrite::instance())

通过对象进行抽象

<?php
#[\Dapr\State\Attributes\StateStore('statestore', \Dapr\consistency\EventualLastWrite::class)]
class MyState {
    /**
     * @var string 
     */
    public string $string_value;
    
    /**
     * @var ComplexType[] 
     */
    #[\Dapr\Deserialization\Attributes\ArrayOf(ComplexType::class)] 
    public array $complex_type;
    
    /**
      * @var SomeObject 
      */
    public SomeObject $object_type;
    
    /**
     * @var int 
     */
    public int $counter = 0;

    /**
     * Increment the counter
     * @param int $amount Amount to increment by
     */
    public function increment(int $amount = 1): void {
        $this->counter += $amount;
    }
}

$app = \Dapr\App::create();
$app->post('/my-state/{key}', function (
    string $key, 
    #[\Dapr\Attributes\FromBody] string $body, 
    \Dapr\State\StateManager $stateManager) {
        $stateManager->save_state(store_name: 'store', item: new \Dapr\State\StateItem(key: $key, value: $body));
        $stateManager->save_object(new MyState);
});
$app->get('/my-state/{key}', function(string $key, \Dapr\State\StateManager $stateManager) {
    return $stateManager->load_state(store_name: 'store', key: $key);
});
$app->start();

事务状态

您还可以使用事务状态通过扩展我们的状态对象或直接提交事务与状态对象进行交互。

直接使用客户端

$transaction = [
    \Dapr\Client\StateTransactionRequest::upsert(key: 'key', value: $client->serializer->as_json($new_value)),
    \Dapr\Client\StateTransactionRequest::delete(key: 'key');
];
$client->executeStateTransaction(storeName: 'statestore', operations: $transaction);

通过对象抽象

#[\Dapr\State\Attributes\StateStore('statestore', \Dapr\consistency\StrongFirstWrite::class)]
class SomeState extends \Dapr\State\TransactionalState {
    public string $value;
    public function ok(): void {
        $this->value = 'ok';
    }
}

$app = Dapr\App::create();
$app->get('/do-work', function(SomeState $state) {
    $state->begin();
    $state->value = 'not-ok';
    $state->ok();
    $state->commit();
    return $state;
});
$app->start();

演员

演员已完全实现且功能强大。为了定义一个演员,您必须首先定义接口。您可能希望将其放在单独的库中,以便于其他服务调用。

<?php
/**
 * Actor that keeps a count
 */
 #[\Dapr\Actors\Attributes\DaprType('ExampleActor')]
interface ICounter {
    /**
     * Increment a counter
     */
    public function increment(int $amount): void;
}

一旦定义了接口,您就需要实现行为并注册演员。

<?php

class CountState extends \Dapr\Actors\ActorState {
    public int $count = 0;
}

#[\Dapr\Actors\Attributes\DaprType('Counter')]
class Counter extends \Dapr\Actors\Actor implements ICounter {
    /**
     * Initialize the class
     */
    public function __construct(string $id, private CountState $state) {
        parent::__construct($id);
    }

    /**
     * Increment the count by 1
     */
    public function increment(int $amount): void {
        $this->state->count += $amount;
    }
}

// register the actor with the runtime
$app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions([
    'dapr.actors' => [Counter::class]
]));
$app->start();

要注入的状态从构造函数参数中读取,状态必须从ActorState派生才能注入。您可以使用尽可能多的状态类。如果您在方法调用期间对状态进行任何更改,状态将自动保存。

Actor基类为您提供了访问一些辅助函数的功能,并节省了您编写一些样板代码。您还可以实现IActor并使用ActorTrait

调用演员

要调用演员,只需调用ActorProxy并获取代理对象

<?php
use Dapr\Actors\ActorProxy;

 $app = \Dapr\App::create();
 $app->get('/increment/{actorId}[/{amount:\d+}]', function(string $actorId, ActorProxy $actorProxy, int $amount = 1) {
    $counter = $actorProxy->get(ICounter::class, $actorId);
    $counter->increment($amount);
    $counter->create_reminder('increment', new \Dapr\Actors\Reminder('increment', new DateInterval('PT10M'), data: 10 ));
 });
$app->start();

您还可以调用没有接口的演员

$client->invokeActorMethod(
    httpMethod: 'GET', 
    actor: new \Dapr\Actors\ActorReference(id: 'id', actor_type: 'Counter'), 
    method: 'increment', 
    parameter: 1
 );

演员限制

  1. 默认情况下,演员没有重新进入功能。您需要在ActorConfigDapr中启用它。
  2. 按设计,静态函数不起作用。
  3. 调用“getter”函数有开销。

更多详情请参阅:https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/

发布/订阅

在您的应用程序中传递事件是任何应用程序的重要方面。Dapr支持此功能,并在本SDK中实现。

发布

为了发布事件,您只需实例化Topic对象

<?php
$app = \Dapr\App::create();
$app->get('/publish', function(\Dapr\Client\DaprClient $client) {
    $topic = new \Dapr\PubSub\Topic(pubsub: 'pubsub', topic: 'topic', client: $client);
    $topic->publish(['message' => 'arrive at dawn']);
});
$app->start();

或者您可以使用新的客户端,如下所示

$client->publishEvent(pubsubName: 'pubsub', topicName: 'topic', data: ['message' => 'arrive at dawn'], contentType: 'application/json');

订阅

$app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions([
    'dapr.subscriptions' => [new \Dapr\PubSub\Subscription('redis-pubsub', 'my-topic', '/receive-message')]
]));
$app->post('/receive-message', function(#[\Dapr\Attributes\FromBody] \Dapr\PubSub\CloudEvent $event) {
 // do something
});
$app->start();

序列化

如果您需要注册自定义序列化程序,您可以根据类型完全覆盖内置序列化程序,甚至覆盖默认序列化程序

// register a custom type serializer
$app = \Dapr\App::create(configure: fn(\DI\ContainerBuilder $builder) => $builder->addDefinitions([
    'dapr.serializers.custom' => [MyType::class => [MyType::class, 'serialize']],
    'dapr.deserializers.custom' => [MyType::class => [MyType::class, 'deserialize']],
]));

// get the serializer to do manual serializations
$app->get('/', function(\Dapr\Serialization\ISerializer $serializer) {
    return $serializer->as_array('anything here');
});
$app->start();

使用新客户端

$client = \Dapr\Client\DaprClient::clientBuilder()
    ->withDeserializationConfig($configuration)
    ->withSerializationConfig($configuration)
    ->build()

开发

测试

只需运行composer test即可运行单元测试。您可以使用composer lint进行检查。

集成测试

您需要docker-composejq

构建并启动环境,然后运行集成测试并清理。

make

您应该看到如下输出

{
  "/test/actors": {
    "status": {
      "test completed successfully: ": ""
    },
    "results": {
      "Empty actor should have no data: ": "",
      "Actor should have data: ": "",
      "Reminder should increment: ": "",
      "time formats are delivered ok: ": "",
      "Timer should increment: ": "",
      "[object] saved array should match: ": "",
      "[object] saved string should match: ": "",
      "actor can return a simple value: ": ""
    }
  },
  "/test/binding": {
    "status": {
      "test completed successfully: ": ""
    },
    "results": {
      "we should have received at least one cron: ": ""
    }
  },
  "/test/invoke": {
    "status": {
      "test completed successfully: ": ""
    },
    "results": {
      "Should receive a 200 response: ": "",
      "Static function should receive json string: ": ""
    }
  },
  "/test/pubsub": {
    "status": {
      "test completed successfully: ": ""
    },
    "results": {
      "simple-test": {
        "sub received message: ": "",
        "Received this data": {
          "id": "57b4a889-3cbb-4d5d-a6fe-7574b097c34c",
          "source": "dev",
          "specversion": "1.0",
          "type": "com.dapr.event.sent",
          "datacontenttype": "application/json",
          "data": [
            "test_event"
          ],
          "traceid": "00-222a0988ecf9d53a006a35f961229788-6706c11d588c6da2-00"
        },
        "should be valid cloud event: ": ""
      },
      "Testing custom cloud event": {
        "sub received message: ": "",
        "Received this raw data": {
          "id": "123",
          "source": "http://example.com",
          "specversion": "1.0",
          "type": "com.example.test",
          "datacontenttype": "application/json",
          "subject": "yolo",
          "time": "2021-02-21T09:32:35+00:00Z",
          "data": [
            "yolo"
          ],
          "traceid": "00-222a0988ecf9d53a006a35f961229788-a0880db7ab9a97d3-00"
        },
        "Expecting this data": {
          "id": "123",
          "source": "http://example.com",
          "specversion": "1.0",
          "type": "com.example.test",
          "datacontenttype": "application/json",
          "subject": "yolo",
          "time": "2021-02-21T09:32:35+00:00Z",
          "data": [
            "yolo"
          ]
        },
        "Received this decoded data": {
          "id": "123",
          "source": "http://example.com",
          "specversion": "1.0",
          "type": "com.example.test",
          "datacontenttype": "application/json",
          "subject": "yolo",
          "time": "2021-02-21T09:32:35+00:00Z",
          "data": [
            "yolo"
          ]
        },
        "Event should be the same event we sent, minus the trace id.: ": ""
      },
      "Publishing raw event": {
        "sub received message: ": "",
        "Received this data": {
          "id": "01e9fef4-7774-4684-9249-cdce032a2713",
          "source": "dev",
          "specversion": "1.0",
          "type": "com.dapr.event.sent",
          "datacontenttype": "application/json",
          "data": {
            "datacontenttype": "text/xml",
            "data": "<note><to>User1</to><from>user2</from><message>hi</message></note>",
            "specversion": "1.0",
            "type": "xml.message",
            "source": "https://example.com/message",
            "subject": "Test XML Message",
            "id": "id-1234-5678-9101",
            "time": "2020-09-23T06:23:21Z"
          },
          "traceid": "00-222a0988ecf9d53a006a35f961229788-d90a5a50ffa8c104-00"
        }
      },
      "Binary response": {
        "raw": {
          "id": "3a5936e4-9bce-41e5-b344-f7e9bfb186f5",
          "source": "dev",
          "specversion": "1.0",
          "type": "com.dapr.event.sent",
          "datacontenttype": "application/json",
          "data": "raw data",
          "traceid": "00-222a0988ecf9d53a006a35f961229788-d579f3a753fa6b45-00"
        },
        "Data properly decoded: ": ""
      }
    }
  },
  "/test/state/concurrency": {
    "status": {
      "test completed successfully: ": ""
    },
    "results": {
      "initial value correct: ": "",
      "Starting from 0: ": "",
      "last-write update succeeds: ": "",
      "first-write update fails": ""
    }
  },
  "/test/state": {
    "status": {
      "test completed successfully: ": ""
    },
    "results": {
      "state is empty: ": "",
      "initial state is correct: ": "",
      "saved correct state: ": "",
      "properly loaded saved state: ": "",
      "prefix should work: ": "",
      "single key read with default: ": "",
      "single key write: ": ""
    }
  }
}