Blame view

泰额版/Food Labeling Management Code/Yi.Doc.Md/02.框架功能模块教程/16.事件总线.md 6.66 KB
59e51671   “wangming”   1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  ## 本地事件总线
  本地事件总线允许服务发布和订阅进程内事件. 这意味着如果两个服务(发布者和订阅者)在同一个进程中运行,那么它是合适的.
  
  ### 发布事件
  以下介绍了两种发布本地事件的方法.
  
  ILocalEventBus
  可以注入 ILocalEventBus 并且使用发布本地事件.
  
  示例: 产品的存货数量发生变化时发布本地事件
  ``` cs
  using System;
  using System.Threading.Tasks;
  using Volo.Abp.DependencyInjection;
  using Volo.Abp.EventBus.Local;
  
  namespace AbpDemo
  {
      public class MyService : ITransientDependency
      {
          private readonly ILocalEventBus _localEventBus;
  
          public MyService(ILocalEventBus localEventBus)
          {
              _localEventBus = localEventBus;
          }
          
          public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
          {
              //TODO: IMPLEMENT YOUR LOGIC...
              
              //PUBLISH THE EVENT
              await _localEventBus.PublishAsync(
                  new StockCountChangedEvent
                  {
                      ProductId = productId,
                      NewCount = newCount
                  }
              );
          }
      }
  }
  ```
  PublishAsync 方法需要一个参数:事件对象,它负责保持与事件相关的数据,是一个简单的普通类:
  ``` cs
  using System;
  
  namespace AbpDemo
  {
      public class StockCountChangedEvent
      {
          public Guid ProductId { get; set; }
          
          public int NewCount { get; set; }
      }
  }
  ```
  即使你不需要传输任何数据也需要创建一个类(在这种情况下为空类).
  ### 订阅事件
  一个服务可以实现 ILocalEventHandler<TEvent> 来处理事件.
  
  示例: 处理上面定义的StockCountChangedEvent
  ``` cs
  using System.Threading.Tasks;
  using Volo.Abp.DependencyInjection;
  using Volo.Abp.EventBus;
  
  namespace AbpDemo
  {
      public class MyHandler
          : ILocalEventHandler<StockCountChangedEvent>,
            ITransientDependency
      {
          public async Task HandleEventAsync(StockCountChangedEvent eventData)
          {
              //TODO: your code that does somthing on the event
          }
      }
  }
  ```
  这就是全部,MyHandler 由ABP框架自动发现,并在发生 StockCountChangedEvent 事件时调用 HandleEventAsync.
  
  事件可以由0个或多个处理程序订阅.
  一个事件处理程序可以订阅多个事件,但是需要为每个事件实现 ILocalEventHandler<TEvent> 接口.
  
  > 事件处理程序类必须注册到依赖注入(DI),示例中使用了 ITransientDependency. 参阅依赖注入文档了解更多选项.
  
  ### 事务和异常行为
  当一个事件发布,订阅的事件处理程序将立即执行.所以;
  
  如果处理程序抛出一个异常,它会影响发布该事件的代码. 这意味着它在 PublishAsync 调用上获得异常. 因此如果你想隐藏错误,在事件处理程序中使用try-catch. *如果在一个工作单元范围内执行的事件发布的代码,该事件处理程序也由工作单元覆盖. 这意味着,如果你的UOW是事务和处理程序抛出一个异常,事务会回滚.
  
  ## 分布式事件总线
  分布式事件总线系统提供了一个可以被任何提供程序实现的抽象. 有四种开箱即用的提供程序:
  
  - LocalDistributedEventBus 是默认实现,实现作为进程内工作的分布式事件总线. 是的!如果没有配置真正的分布式提供程序,默认实现的工作方式与本地事件总线一样.
  - RabbitMqDistributedEventBus 通过RabbitMQ实现分布式事件总线
  - KafkaDistributedEventBus 通过Kafka实现分布式事件总线
  - RebusDistributedEventBus 通过Rebus实现分布式事件总线
  - 
  使用本地事件总线作为默认具有一些重要的优点. 最重要的是:它允许你编写与分布式体系结构兼容的代码. 您现在可以编写一个整体应用程序,以后可以拆分成微服务. 最好通过分布式事件而不是本地事件在边界上下文之间(或在应用程序模块之间)进行通信.
  
  ### 发布事件
  IDistributedEventBus
  可以注入 IDistributedEventBus 并且使用发布分布式事件.
  
  示例: 产品的存货数量发生变化时发布分布式事件
  ``` cs
  using System;
  using System.Threading.Tasks;
  using Volo.Abp.DependencyInjection;
  using Volo.Abp.EventBus.Distributed;
  
  namespace AbpDemo
  {
      public class MyService : ITransientDependency
      {
          private readonly IDistributedEventBus _distributedEventBus;
  
          public MyService(IDistributedEventBus distributedEventBus)
          {
              _distributedEventBus = distributedEventBus;
          }
          
          public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
          {
              await _distributedEventBus.PublishAsync(
                  new StockCountChangedEto
                  {
                      ProductId = productId,
                      NewCount = newCount
                  }
              );
          }
      }
  }
  ```
  PublishAsync 方法需要一个参数:事件对象,它负责保持与事件相关的数据,是一个简单的普通类:
  ``` cs
  using System;
  
  namespace AbpDemo
  {
      [EventName("MyApp.Product.StockChange")]
      public class StockCountChangedEto
      {
          public Guid ProductId { get; set; }
          
          public int NewCount { get; set; }
      }
  }
  ```
  即使你不需要传输任何数据也需要创建一个类(在这种情况下为空类).
  
  > Eto 是我们按照约定使用的Event Transfer Objects(事件传输对象)的后缀. s虽然这不是必需的,但我们发现识别这样的事件类很有用(就像应用层上的DTO 一样).
  
  ### 订阅事件
  一个服务可以实现 IDistributedEventHandler<TEvent> 来处理事件.
  
  示例: 处理上面定义的StockCountChangedEto
  ``` cs
  using System.Threading.Tasks;
  using Volo.Abp.DependencyInjection;
  using Volo.Abp.EventBus.Distributed;
  
  namespace AbpDemo
  {
      public class MyHandler
          : IDistributedEventHandler<StockCountChangedEto>,
            ITransientDependency
      {
          public async Task HandleEventAsync(StockCountChangedEto eventData)
          {
              var productId = eventData.ProductId;
          }
      }
  }
  ```
  这就是全部.
  
  MyHandler 由ABP框架自动发现,并在发生 StockCountChangedEto 事件时调用 HandleEventAsync.
  如果你使用的是分布式消息代理,比如RabbitMQ,ABP会自动订阅消息代理上的事件,获取消息执行处理程序.
  如果事件处理程序成功执行(没有抛出任何异常),它将向消息代理发送确认(ACK).
  你可以在处理程序注入任何服务来执行所需的逻辑. 一个事件处理程序可以订阅多个事件,但是需要为每个事件实现 IDistributedEventHandler<TEvent> 接口.
  
  事件处理程序类必须注册到依赖注入(DI),示例中使用了 ITransientDependency. 参阅DI文档了解更多选项.