In the end it’s not really that different, the main adavantage is that it masquerades away implementation details.
The use case modeled in that part of the workshop is intended to demonstrate integration with 3rd parties. In which case services one way or another must expose data. On one hand there are your well designed services and boundaries on the other hand there is one service, the 3rd party one, that simply doesn’t speak your language. Also your well defined information are crossing a boundary leaving your world for something you have no control over.
Mainly because Shipping should not be responsible to talk, from the technical perspective, to the 3rd party. Shipping owns the intent to ship with FedEx, not the technical details about how to talk to FedEx. Given that multiple services are involved in talking to the 3rd party it makes more sense top host it under the ITOps umbrella.
Going back to the interface implementation details, there nothing wrong in reversing the responsibilities, by doing something like:
class ShipWithFedExCommandHandler : ....
public ShipWithFedExCommandHandler( IEnumerable<IProvideFedExInfo> providers)
public async Task Handle( msg, context )
//create empty xml doc
foreach( var provider in providers )
await provider.AppendInfo( /* previously created xml doc */ );
//send doc to FedEx
In the above sample the shipment process knows nothing about info providers, this approach requires, for example, that the order in which providers are invoked is not important.