9 ms·
The scariest thing mentioned here is when Team Email switches from a Service Object to a Micro Service. Actually, it's not that scary as part of email where the
by tel 12y ago
The scariest thing mentioned here is when Team Email switches from a Service Object to a Micro Service. Actually, it's not that scary as part of email where the user of that object always anticipates potential failure (and probably has next to no recourse if failure occurs).
So instead we could pretend that it's something like MachineLearningProvider. When this converts to a Micro Service its failure space increases by an order of magnitude without expressing it in the interface.
That was the primary problem with RPC—you really don't want to confuse local function calls from remote ones.
- yummyfajitas 12y agoThis is generally solved with monadic design. Results are returned wrapped in some monad - Future[Result] in this case. The monad generally expresses the failure modes. When you switch from, e.g. `LocalMachineLearningProvider` to `RemoteMachineLearningProvider`, your interface will change from MachineLearningResult to Future[MachineLearningResult].
- tbrownaw 12y agoAnd then instead of mostly working (when the network isn't down), your code fails to compile and doesn't work at all. And you have to either spend just as much time reworking things to handle the errors in a user-acceptable manner, or take shortcuts and stub out the error handling to either die or fake things and keep going (ie, to mostly work as long as the network isn't down).
- tel 12y agoCertainly, or even with two layers of futures. Notably, this changes the interface. I'm not saying it's not solvable, just that it was so silently mentioned in the article. You should have to work to reproof your code after RPCing something. It can just be scary if you've spent time making it out as though MchineLearningProvider can never fail. Interacting with an outside object can be a painful experience. This gives you some of the worries of a true distributed system within your monolith.
- yummyfajitas 12y agoIf only there were a way of transforming F[F[X]] to F[X]. Then we could avoid nested futures!
- jeremyjh 12y agoEspecially if it had some nice syntax sugar. Then we could chain together as many as we want!
- tel 12y agoYou're being sarcastic, but I meant to suggest that there would be value in running those layers independently so that you don't combine errors. If you present it as F[X] then you've thrown away potentially interesting information. IO (IO ()) is pretty valuable, for instance.
- yummyfajitas 12y agoJust curious, what is the advantage? Near as I can tell EmailProvider will return one of two sorts of errors - either a ServiceError (the microservice was unable to send) or a NetworkError (unable to talk to micro service). These will be represented in the class of the exception the future wraps. What benefit does two levels of futures provide? I was sarcastic before, but now I'm honestly asking.
- tel 12y agoThe two differences I can think of are 1. If you don't assume exception throwing (which may be valuable) then you can more easily distinguish one failure from the other. 2. It reflects in the type a more accurate representation of the effect going on. If such distinctions are undesirable it's a trivial `join` to eliminate them but if the distinction is never exposed then you can't get it back. So really, my thought is that given greater opportunity to distinguish between end-user meaningful effects it's worth exposing them by default. It should be a deliberate choice to hide them with join.
- deleted 12y ago
- beberlei 12y agoSo you are sugessting better prematurely "optimizing" every call to be remote from the beginnig? Wouldnt anyone wrap remote services behind a facade anyways instead of directly calling a http client? I fail to see how this leads to an interface that expresses the remoteness more in most of the cases.
- tel 12y agoI'm suggesting that you can't always abstract away things the way you'd like. The idea of how to send an email is highly abstractable, the idea of some other entity doing it leaks and rightly should.
- bunderbunder 12y ago> When this converts to a Micro Service its failure space increases by an order of magnitude without expressing it in the interface. I think you just emphasized a bit that, in principle, should never be allowed to happen. When Team Email switches to a service their interface has changed, period. In some idealized sense the contract they implement is the same, sure, but an interface that allows for the possibility of timeouts, down servers, lost packets, etc. is fundamentally different from one that doesn't, and you can't (or at least shouldn't) use the same code to interact with the two. Ideally, the compiler wouldn't even let you try - and in a good type-safe language, this is accomplished if Team Email swaps out the interface they're implementing when they go service.
- tel 12y agoOh totally agreed! I was bothered that the essay emphasized doing this without "changing the interface"! At least not the types.