Was working on an interesting problem today related to file transfers. An original service just tossed the file our way. The new service can break it into pieces and send the pieces.
Of course, I could have easily just created a temporary file with a special extension and kept appending bytes until the last chunk came, then copy the file, but this felt a little "hacked." Is there a cleaner way to separate the need to piece something together from the separate concerns of receiving something on the wire and ultimately persisting it? What about exceptions? What if the last pieces comes first or we suddenly stop receiving pieces at all?
After digging around I realized we were dealing with the saga pattern. There is a great document online that gives an example of the saga pattern. To the best of my knowledge (and I admit I could have it wrong) a saga is a long-lived transaction that may involve multiple services and can ultimately reach some consensus on when the transaction is complete.
In this case, we might be having that conversation with the same service (the one uploading the file) but the fact is, the conversation is around one topic (the file) and has several states (hello ... the story ... goodbye). We "agree" we're done when the conversation is concluded (i.e. the file is uploaded) or the other person walks away (we don't receive another piece after a given amount of time ... something we also need to "agree" upon to end the transaction as "timed out."
What makes then even more interesting is the composite pattern applied to the target. It seems to me I should be able to define an IPiece<T> where T is something I'm dealing with composing over time. I can then create an IPieceAssembler<T,U> where T: IPiece<U> where U: class ... basically the assembly has an Append
method and an Assemble
method that returns T. Finally, I can have an IAssembleSaga that represents the long-lived transaction with a BeginSaga(IPiece<T>), ContinueSaga, and EndSaga that returns a T or AbortSaga that cleans up. The saga is responsible for persistence until end saga is called and then cleans itself up, and some controller on top of sage will end the saga.
Now we have something interesting that can be re-used elsewhere as well ... hmmm!
This was all very exciting until my mentor brought me back down to earth. I was ... (shhh, don't tell) over-engineering! He made a great point: why am I taking so much time to build interfaces and abstractions when I'm not even sure about the concrete implementation? Turns out a generic is useless if it's not used anywhere else, otherwise just point to the type. Agile says we incrementally do what we know and don't try too hard to plan ahead ... if we come to that second case we can always extract an interface, create a generic type and refactor then, right? And sure enough, when I focused on the concrete implementation I realized there was no saga. I ended up writing bytes to disk.