我正在使用队列中的消息,并且我希望能够从API请求中执行相同的逻辑,因此我正在尝试从OnMessage()
方法中提取用例。
public class QueueListener
{
public void OnMessage(Message message)
{
var thing = _apiClient.GetThing(message.Id);
var stuff = _repository.GetStuff(message.Id);
stuff.PutAThingInStuff(thing);
_repository.SaveStuff(stuff);
_outQueue.SendOutgoingMessage(new Message(message.Id));
_apiClient.SetThingToComplete(message.Id);
}
}
然而,.SendOutgoingMessage()
和标记.SetThingToComplete()
的顺序让我有了第二次猜测。只有从队列调用用例时才实现发送到传出队列,而不是从API调用用例时。API将简单地返回用例的结果。此外,顺序是重要的,如果我们不想在没有成功将其发布到传出队列的情况下将其标记为完成(我知道我不能保证不会有错误将结果返回给API的消费者(。
我不确定这里的正确方法是什么,我觉得.SetThingToComplete()
属于用例,因为无论在哪里调用,都应该发生这种情况。
我认为在这种情况下使用呈现器可能是有意义的,但我担心向.Present()
和.SetThingToComplete()
添加特定的顺序可能会添加隐式耦合,并成为泄漏的抽象。
public class UseCase
{
public void Handle(Request request, IPresenter presenter)
{
var thing = _apiClient.GetThing(request.MessageId);
var stuff = _repository.GetStuff(request.MessageId);
stuff.PutAThingInStuff(thing);
_repository.SaveStuff(stuff);
// does having a specific order here implicitly add coupling?
presenter.Present(new Result(request.MessageId));
_apiClient.SetThingToComplete(request.MessageId);
}
}
public class QueueListener
{
public void OnMessage(Message message)
{
var presenter = _serviceProvider.GetRequiredService<IPresenter>();
var useCase = _serviceProvider.GetRequiredService<UseCase>();
useCase.Handle(new Request(message.Id), presenter);
}
public class Presenter : IPresenter
{
private readonly IOutBoundQueue _outQueue;
public void Present(Result result)
{
_outQueue.SendOutgoingMessage(new Message(result.MessageId));
}
}
}
我认为它们可能是两个不同的用例,但是除了一行代码外,所有的代码都是相同的。考虑过向CCD_ 7添加一个标志以发送到队列,但我不喜欢添加";配置";属性到与业务逻辑无关的命令。
我还考虑添加一个额外的EventHandler类来处理事件,而不是调用命令本身或使用域事件,但在这两种情况下都会出现相同的问题。
您应该认真考虑的可能性是,您可能还没有足够的信息来创建在长期中健康的抽象
错误的抽象比根本没有抽象更具破坏性。等待胜过猜测。——Sandi Metz,2012
等待的好处之一是,您可以发现使用代码的两个不同地方是否真的因为相同的原因而发生了变化。
但是,假设你现在必须猜测。。。
解耦的部分技巧是尽可能少地共享信息。
传递东西是正确的想法:
public class QueueListener
{
public void OnMessage(Message message)
{
var verb = this.verb(message.Id);
var useCase = _serviceProvider.GetRequiredService<UseCase>();
useCase.Handle(message.Id, verb);
}
IVerb verb(Message.Id id)
{
return new Verber(this._outQueue, id)
}
public class Verber : IVerb
{
private readonly IOutBoundQueue _outQueue;
private readonly Message.Id _id;
Verber(IOutBoundQueue outQueue, Message.Id id)
{
this._outQueue = outQueue
this._id = id
}
public void Verb()
{
_outQueue.SendOutgoingMessage(new Message(this._id));
}
}
}
public class UseCase
{
public void Handle(Request request, IVerb verber)
{
var thing = _apiClient.GetThing(request.MessageId);
var stuff = _repository.GetStuff(request.MessageId);
stuff.PutAThingInStuff(thing);
_repository.SaveStuff(stuff);
verber.verb();
_apiClient.SetThingToComplete(request.MessageId);
}
}
因此,我们可以通过在抽象背后包装特定于某个专业化的信息来减少(部分(耦合,这样代码的其他部分就不需要知道它(请参见Parnas 1971中的信息隐藏(。
这里有一个特定的顺序会隐含地增加耦合吗?
有点。
您的接口是契约的表现形式,其中契约可能包括类型系统所表达的前提条件和后条件之外的先决条件和后决条件。
class UseCase : Contract.UseCase {...}
class QueueListener {
class Verber : Contract.Verb { ... }
...
}
从某种意义上说,合同定义了在某种互动中成为一个合格的参与者意味着什么;未能履行合同义务的参与者被违约。
换句话说;隐含性;这只是在合同不明确的情况下选择设计的结果。
一个可能为人所熟悉的显式合同的例子是:HTTP。HTTP组件之间没有紧密耦合,但它们耦合到协议和已标准化的消息语义。
在大多数情况下,这是非常成功的,因为HTTP协议非常稳定,在协议发生变化的地方,它们以向后兼容的方式进行了更改。
但设计稳定的合同是一个难题,尤其是当我们正在探索我们的第一个实现时,我们仍然不知道未来可能需要哪些类型的更改。当合同是隐含的时,这就更具挑战性;但是,当需求不稳定时,明确的合同定义对于一个短期工件来说是一项艰巨的工作。
等待胜过猜测。
您正在API之上构建QueueListener,但它们应该并行。它们都应该运行相同的用例,但它们是两个不同的接口适配器。所有与用例处理相关的代码都是通用的,应该放在同一个用例中,并且应该与适配器无关:
public class UseCase
{
public void Handle(Request request, IPresenter presenter)
{
var stuff = _repository.GetStuff(request.MessageId);
stuff.PutAThingInStuff(thing);
_repository.SaveStuff(stuff);
presenter.Present(new Result(request.MessageId));
}
}
然后让两个适配器处理通信:
public class QueueListener
{
public void OnMessage(Message message)
{
var presenter = _serviceProvider.GetRequiredService<IPresenter>();
var useCase = _serviceProvider.GetRequiredService<UseCase>();
useCase.Handle(new Request(message.Id), presenter);
}
public class QueuePresenter : IPresenter
{
private readonly IOutBoundQueue _outQueue;
public void Present(Result result)
{
_outQueue.SendOutgoingMessage(new Message(result.MessageId));
}
}
}
public class ApiController : Controller
{
[HttpPost]
public IActionResult OnPost([FromQuery] Message message)
{
var presenter = _serviceProvider.GetRequiredService<IPresenter>();
var useCase = _serviceProvider.GetRequiredService<UseCase>();
presenter.Controller = this;
useCase.Handle(new Request(message.Id), presenter);
return presenter.Result;
}
public class ApiPresenter : IPresenter
{
public Controller Controller { get; set; }
public IActionResult Result { get; private set; }
public void Present(Result result)
{
Result = Controller.Ok(result.MessageId);
}
}
}