|
|
|
|
|
by ecdavis
1208 days ago
|
|
I've used this for many real applications that I would characterise as small (biggest being ~10KLOC excluding tests, types, etc.). I've applied the pattern across entire codebases and to specific components within much larger codebases which follow other patterns. I tend to combine it with some of the ideas in Mitchell Hashimoto's talk Advanced Testing with Go[0] - particularly small interfaces and defining test stubs alongside real implementations. In practice my imperative shell tends to have two layers. The inner layer is responsible for executing the imperative logic, while the outer layer is responsible for initialising configuration and dependencies, invoking the inner layer, and adhering to any sort of external interface that it may need to satisfy. Everything from the inner layer down through the functional core can be comprehensively tested using stub objects only -- no need to patch anything. Unfortunately everything I've applied this pattern to is proprietary, so I can't share any code examples. [0] https://www.youtube.com/watch?v=yszygk1cpEc |
|
It was mostly about putting the "imperative" part closer to main.go and putting business rules and things like that in the other files: Routing, serialization, validation, business rules, data transformation, command line argument parsing, configuration parsing, they all go in the "functional" part. Instantiating the HTTP server, reading argv, files and reading/writing to the database goes in the "imperative" part.
The major hurdle for me is frameworks and libraries that often want to be used in an imperative way. Some routers, for, instance could be purely functional, but they often want to instantiate the HTTP server themselves. Not a big issue in practice in Golang, though.
Also: the database. To keep purity in a simple way, you gotta wrap the imperative parts and use callback-ish structures. This part is often where I cheat. But with a proper abstraction it's great. I hate to say the forbidden word here, but a "monadic" abstraction you can have your cake and eat it. I never did it in Golang and I have no idea whether it would work, but, I did it in C# and it was a breeze.
I agree with your assessment about testing. In the end it was incredibly easy for me to get 100% coverage on the functional part.