gdbots / pbjx-bundle
Symfony 扩展包,集成了 gdbots/pbjx 消息工具。
Requires
- php: >=8.1
- gdbots/pbjx: ^4.2
- gdbots/uri-template: ^2.0
- symfony/console: ^6.4 || ^7.0
- symfony/framework-bundle: ^6.4 || ^7.0
- twig/twig: ^3.8
Requires (Dev)
- aws/aws-sdk-php-symfony: ^2.6
- gdbots/acme-schemas: ^3.0
- phpunit/phpunit: ^10.5
- ruflin/elastica: ^7.1
README
Symfony 扩展包,集成了 gdbots/pbjx 库。
配置
按照标准 bundle install 使用 gdbots/pbjx-bundle 作为 composer 包名。默认配置为所有发送、发布、请求操作提供内存处理。EventStore 和 EventSearch 默认未配置。
以下示例假设您正在运行 DynamoDb EventStore 和 Elastica EventSearch。这些是可选配置。
config/packages/pbjx.yml
# many of the configurations below are defaults so you can remove them # from your configuration, added here for reference parameters: # optionally defined as parameter here to allow reuse es_clusters: default: debug: '%kernel.debug%' timeout: 300 # default persistent: true # default round_robin: true # default servers: - {host: '127.0.0.1', port: 9200} # default gdbots_pbjx: # ensure tamper proof messaging by signing all pbjx messages # e.g. AWS Lambda to your API servers (signed with shared secret) # or by using bearer tokens in SPA to sign messages pbjx_token_signer: default_kid: '%env(PBJX_CURRENT_SIGNING_KID)%' # multiple keys allow for seamless key rotation keys: - {kid: '%env(PBJX_NEXT_SIGNING_KID)%', secret: '%env(PBJX_NEXT_SIGNING_SECRET)%'} - {kid: '%env(PBJX_CURRENT_SIGNING_KID)%', secret: '%env(PBJX_CURRENT_SIGNING_SECRET)%'} pbjx_controller: # if accepting commands from web trackers (analytics, click tracking, etc.) # then you may want to enable "GET" requests allow_get_request: false # default # array of curies or regex patterns that will not require x-pbjx-token validation bypass_token_validation: ['gdbots:pbjx:request:echo-request'] # default # the receive controller accepts transport messages. it is ideally secured # in a VPC and only called by internal services. pbjx_receive_controller: enabled: false # default, ensure pbjx_token_signer.keys are set prior to enabling command_bus: transport: ~ # in_memory, firehose, kinesis event_bus: transport: ~ # in_memory, firehose, kinesis request_bus: # requests must return a value, so firehose and kinesis simply run # the request in memory as they don't support request/response. transport: ~ # in_memory event_store: provider: dynamodb dynamodb: table_name: acme-event-store event_search: provider: elastica elastica: # your app will at some point need to customize the queries # override the class so you can provide these customizations. class: Acme\Pbjx\EventSearch\Elastica\ElasticaEventSearch query_timeout: '500ms' # default clusters: '%es_clusters%' index_manager: # to customize index mapping class: Acme\Pbjx\EventSearch\Elastica\IndexManager scheduler: provider: dynamodb dynamodb: table_name: acme-scheduler state_machine_arn: 'arn:aws:states:%cloud_region%:%aws_account_id%:stateMachine:acme-%app_env%-pbjx-scheduler' # typically these would be in services.yml file. services: # unless you're starting with dynamodb streams publishing events you will need this. gdbots_pbjx.event_store.dynamodb_2pc: class: Gdbots\Pbjx\EventStore\TwoPhaseCommitEventStore decorates: gdbots_pbjx.event_store.dynamodb arguments: - '@pbjx' - '@gdbots_pbjx.event_store.dynamodb_2pc.inner' - '%env(PBJX_DISABLE_2PC_EVENT_STORE)%' public: false # If you are using AWS ElasticSearch service, use AwsAuthV4ClientManager # UPDATE as of November 2017, AWS supports ES in a VPC; this is preferable # to using IP rules and/or AwsAuthV4ClientManager. gdbots_pbjx.event_search.elastica.client_manager: class: Gdbots\Pbjx\EventSearch\Elastica\AwsAuthV4ClientManager public: true arguments: - '@aws_credentials' - '%cloud_region%' - '%es_clusters%' - '@logger' tags: - {name: monolog.logger, channel: pbjx.event_search}
在您的本地环境中,强烈建议配置 PbjxDebugger。
config/services_local.yml
services: monolog_json_formatter: class: Monolog\Formatter\JsonFormatter arguments: ['!php/const:Monolog\Formatter\JsonFormatter::BATCH_MODE_NEWLINES'] monolog: handlers: pbjx_debugger: type: stream path: '%kernel.logs_dir%/pbjx-debugger.log' level: debug formatter: monolog_json_formatter channels: ['pbjx.debugger']
Pbjx HTTP 端点
Pbjx 已准备好在您的应用程序和控制台命令中使用,但尚未通过 HTTP 提供。 提供 HTTP 功能非常强大,但如果不正确地安全,可能会非常危险。
在保护您的应用程序时,所有常规规则都适用。身份验证和授权由您负责,但是,Symfony 使用 security components 使得这相对简单。
示例安全配置
# see https://symfony.ac.cn/doc/current/security/voters.html pbjx_permission_voter: class: App\Security\PbjxPermissionVoter public: false arguments: ['@security.access.decision_manager'] tags: - {name: security.voter} # see https://symfony.ac.cn/doc/current/components/security/authorization.html#access-decision-manager # use the Gdbots\Bundle\PbjxBundle\Validator\PermissionValidatorTrait to provide some boilerplate. gdbots_pbjx.pbjx_permission_validator: class: AppBundle\Security\PbjxPermissionValidator public: false arguments: ['@request_stack', '@security.authorization_checker'] tags: - {name: pbjx.event_subscriber}
要启用 Pbjx http 端点,您必须包含路由。在 config/routes.yml
pbjx: resource: '@GdbotsPbjxBundle/Resources/config/routes.xml' prefix: /pbjx
一旦设置完成,就可以将 ANY pbjx 消息发送到端点 /pbjx/vendor/package/category/message
。此 URL 是配置的前缀,然后是 SchemaCurie
解析为 URL。
为什么不直接使用
/pbjx
?对于日志记录、授权、负载均衡、调试等,有完整的路径到SchemaCurie
是一个巨大的好处。
示例 curl 请求
curl -X POST -s -H "Content-Type: application/json" "https://yourdomain.com/pbjx/gdbots/pbjx/request/echo-request" -d '{"msg":"test"}' # with PbjxToken signature (use composer package gdbots/pbjx or npm package @gdbots/pbjx to create tokens) curl -X POST -s -H "Content-Type: application/json" -H "x-pbjx-token: TOKENHERE" "https://yourdomain.com/pbjx/acme/blog/command/publish-article" -d '{"id":"123"}'
示例 ajax 请求
$.ajax({ url: '/pbjx/gdbots/pbjx/request/echo-request', type: 'post', contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({msg: 'hello'}), complete: function (xhr) { console.log(xhr.responseJSON); } });
如果您的
SchemaCurie
包含一个空的类别段,请使用 URL 中的 "_" 替换。
控制器
在控制器中使用 Pbjx 的推荐方法是使用 Symfony 依赖注入所需的 pbjx 服务,并在必要时导入 PbjxControllerTrait
以获取将 pbj 渲染到 twig 模板中的辅助方法。
final class ArticleController extends Controller { use PbjxControllerTrait; /** @var Pbjx */ private $pbjx; /** * @param Pbjx $pbjx */ public function __construct(Pbjx $pbjx) { $this->pbjx = $pbjx; } /** * @Route("/articles/{article_id}", requirements={"article_id": "^[0-9A-Fa-f]+$"}) * @Method("GET") * @Security("is_granted('acme:blog:request:get-article-request')") * * @param Request $request * * @return Response */ public function getAction(Request $request): Response { $getArticleRequest = GetArticleRequestV1::create()->set('article_id', $request->attributes->get('article_id')); $getArticleResponse = $this->pbjx->request($getArticleRequest); return $this->renderPbj($getArticleResponse); } }
PbjxControllerTrait::renderPbj
这是一个方便的方法,它接受一个 pbj 消息并使用 pbjTemplate
推导模板名称,然后调用 Symfony 的 render
方法(来自框架包 Controller
)。
模板将具有 pbj
作为变量,这是消息对象本身。
提示: {{ pbj }} 将将消息以 yaml 格式输出,以便在 twig 中轻松调试,或 {{ pbj|json_encode(constant('JSON_PRETTY_PRINT')) }}
PbjxControllerTrait::pbjTemplate
根据提供的消息的架构(pbj 架构)返回一个对模板的引用。这允许为 pbj 消息进行组件样式开发。您正在请求可以渲染您的消息(例如 Article)为“卡片”、“模态框”、“页面”等的模板。
这可以与 gdbots/app-bundle 的
DeviceViewRendererTrait::renderUsingDeviceView
结合使用(renderPbj 方法会自动执行此操作)。
最终结果是模板的命名空间路径引用,该模板符合 namespaced path 和 Symfony 模板命名最佳实践。示例
Twig 扩展
提供了一些 twig 函数来将控制器可以执行的大部分功能暴露给您的 twig 模板。
Twig 函数:pbj_template
根据提供消息(pbj模式)的架构返回一个twig模板的引用。这允许为pbj消息进行组件样式开发。您正在请求一个可以将消息(例如,文章)渲染为“卡片”、“模态框”、“slack_post”等的模板,并且该模板可以是特定于设备的视图(card.smartphone.html.twig)。
示例
{% include pbj_template(pbj, 'card', 'html', device_view) with {'pbj': pbj} %}
Twig函数:pbj_url
返回一个pbj实例的命名URL。这取决于gdbots/uri-template
包,该包提供了一种注册uri模板的方式。这些预期格式为vendor:label.template_name
,例如acme:article.canonical
。
示例
{{ pbj_url(pbj, 'canonical') }}
Twig函数:uri_template_expand
当您需要直接使用自己的变量扩展URI模板时,请使用此函数。
示例
{{ uri_template_expand('acme:article.canonical', {slug: 'some-slug'}) }}
Twig函数:pbjx_request
与您可以在 Twig中嵌入Symfony控制器的方式相同,您也可以在Twig中嵌入pbjx请求。此函数执行$pbjx->request($request);
并返回响应。如果启用调试,则会抛出异常(通常在开发环境中),否则将其记录并返回null。
示例
{% set get_comments_response = pbjx_request('acme:blog:request:get-comments-request', {'article_id': id}) %} {% if get_comments_response %} {% include pbj_template(get_comments_response, 'list', device_view) with {'pbj': get_comments_response} %} {% endif %}
控制台命令
此库提供了一些命令,使管理Pbjx服务变得简单。运行Symfony控制台并查找pbjx命令。
pbjx [pbjx:message] Handles pbjx messages (command, event, request) and returns an envelope with the result. pbjx:batch [pbjx:lines] Reads messages from a newline-delimited JSON file and processes them. pbjx:create-event-search-storage Creates the EventSearch storage. pbjx:create-event-store-storage Creates the EventStore storage. pbjx:create-scheduler-storage Creates the Scheduler storage. pbjx:describe-event-search-storage Describes the EventSearch storage. pbjx:describe-event-store-storage Describes the EventStore storage. pbjx:describe-scheduler-storage Describes the Scheduler storage. pbjx:export-events Pipes events from the EventStore to STDOUT. pbjx:reindex-events Pipes events from the EventStore and reindexes them. pbjx:replay-events Pipes events from the EventStore and replays them through pbjx->publish. pbjx:tail-events Tails events from the EventStore for a given stream id and writes them to STDOUT.
最有用的可能是pbjx和pbjx:batch命令。这些命令运行pbjx的方式与您在应用程序代码中运行的方式相同,并返回生成的pbj。
Pbjx设计为可以通过cli、应用程序代码和http以相同的方式运行。
console pbjx --pretty 'gdbots:pbjx:request:echo-request' '{"msg":"hello"}'
示例响应
{ "_schema": "pbj:gdbots:pbjx::envelope:1-0-0", "envelope_id": "5d87da8b-b3b5-4e2f-9f60-843e79b678dc", "ok": true, "code": 0, "http_code": 200, "etag": null, "message_ref": { "curie": "gdbots:pbjx:request:echo-response", "id": "aacc60a0-92a5-4ee6-9aec-63b149abcf1d" }, "message": { "_schema": "pbj:gdbots:pbjx:request:echo-response:1-0-0", "response_id": "aacc60a0-92a5-4ee6-9aec-63b149abcf1d", "created_at": "1488149039527239", "ctx_request_ref": { "curie": "gdbots:pbjx:request:echo-request", "id": "c6867d0c-0c97-4e27-903f-30bbd69da79c" }, "ctx_request": { "_schema": "pbj:gdbots:pbjx:request:echo-request:1-0-0", "request_id": "c6867d0c-0c97-4e27-903f-30bbd69da79c", "occurred_at": "1488149039229879", "ctx_retries": 0, "ctx_correlator_ref": { "curie": "gdbots:pbjx::envelope", "id": "5d87da8b-b3b5-4e2f-9f60-843e79b678dc" }, "ctx_app": { "_schema": "pbj:gdbots:contexts::app:1-0-0", "vendor": "acme", "name": "blog-php.console", "version": "v0.1.0", "build": "1488148956" }, "ctx_cloud": { "_schema": "pbj:gdbots:contexts::cloud:1-0-0", "provider": "private", "region": "us-west-2", "zone": "us-west-2a", "instance_id": "123456", "instance_type": "vbox" }, "ctx_ip": "127.0.0.1", "ctx_ua": "pbjx-console\/0.x", "msg": "hello" }, "ctx_correlator_ref": { "curie": "gdbots:pbjx::envelope", "id": "5d87da8b-b3b5-4e2f-9f60-843e79b678dc" }, "msg": "hello" } }
有关pbjx命令的更多详细信息,请参阅--help
。
库开发
Pbj有一个名为混入的概念,它只是一个可以添加到其他架构的模式。这种策略对于创建一致的数据结构以及允许在混入而不是具体架构上执行库开发非常有用。
混入不能单独使用来创建消息,它必须添加到不是混入的架构中。
此捆绑包提供了一个编译器遍历,可以自动注册pbjx命令和请求的处理程序。
示例
虚构的WidgetCo通过创建混入和库来为网站创建小部件,以提供这些混入的实现。
- WidgetCo有一个名为
widgetco:blog:mixin:add-comment
的混入 - WidgetCo有一个名为
WidgetCo\Blog\AddCommentHandler
的处理程序,它使用标记接口Gdbots\Pbjx\DependencyInjection\PbjxHandler
。 - WidgetCo的处理程序使用
findOne
或findAll
方法来返回它可以处理的全部SchemaCurie
对象。
您的公司Acme现在有一个博客,并希望使用WidgetCo混入以及WidgetCoBlogBundle提供的实现。
- Acme创建了一个名为
acme:blog:command:add-comment
的具体架构,该架构使用混入widgetco:blog:mixin:add-comment
- 当pbjx发送命令时,它将查找处理
acme:blog:command:add-comment
curie的服务。 - 该服务可以被symfony自动配置
如果您想扩展或替换widgetco提供的功能,可以实施自己的处理程序,并使用PbjxHandler
接口。
您可以在库中提供具体架构和实现。这两种策略都有其优点和缺点,最大的问题是,如果库不是使用混入开发的,则在应用程序级别对架构的定制将不那么容易。
Pbjx 本身是一个基于混入(mixins)构建的库,用于处理 Command
、Request
和 Event
消息。