10 ms·
Golang web service framework with DI, Telemetry and more
- jrockway 6y agoI have my own take on this: https://github.com/jrockway/opinionated-server https://github.com/jrockway/opinionated-server Some things we do differently, and some things we do the same. We give each RPC handler a logger, so you know where logs are coming from (and the trace ID associated with every message). We collect detailed prometheus metrics for every RPC, and send everything we know to Jaeger. We provide clients for outgoing RPCs that log and trace in the same way as incoming requests. We pick concrete types for logging/tracing/metrics, which makes it easier to figure out what code is actually running. Having used the abstraction layers, I find that when they don't do what you want, it can take a long time to figure out why. If you have to switch from Prometheus to Statsd or something, the performance and configuration characteristics of your app change, so you'll probably want to manually revisit every metric. It's a straightforward refactoring that you will probably never do more than once, so I don't think it's worth making it "easy" out of the box. Finally, we let you configure your app with either flags or environment variables. Generally, I like flags for running things while developing (go run main.go --foo=bar) and environment variables for production. Why pick one or the other when you can do both! Extensive care has been taken to be able to log everything, including all payloads for bidirectional streams. (HTTP logging is a bit of a disaster; check out the code you have to write to be able to hook every type of http.ResponseWriter without the consumer noticing: https://github.com/prometheus/client_golang/blob/master/prometheus/promhttp/delegator.go#L117 https://github.com/prometheus/client_golang/blob/master/prom....) Some features that are missing or sub-optimal: * A long chain of interceptors for every request is very expensive. They should all be condensed into one wrapper that does "everything" explicitly. I don't think it's worth the debugging complexity to be able to compose a bunch of middleware at runtime; just write a function that wraps every request that does the stuff you want. If you want more stuff, type it in right there. Easy to read, easy for the computer to execute. * There should be fine-grained control over logging at runtime. I think you really want to be able to pass in configuration for every sub-logger, rather than a global log level. For example, you may want to adjust log sampling rate at runtime for a particular component, or upgrade a particular component from INFO to DEBUG for temporary investigation. (Without these controls, people stop writing log statements because they're too spammy, and then you have absolutely nothing to help you with ongoing issues.) * There is absolutely no support for mTLS, which is a crime in this day and age. This is probably the next thing that I'll add; you should be able to watch a volume for changes in TLS key/cert/ca material (so you can rotate keys frequently and without restarting), and require that incoming requests have a certificate trusted by the loaded CA. Outgoing requests should also be marked as "external" or "internal", and use the correct client cert for internal requests. (I somewhat promise to add this soon, because it's sorely needed.) * I don't think applications should support any non-production features for development, like a different logging configuration. opinionated-server apps take a --pretty_logs argument that configures it to print plain text instead of JSON, but I don't use it anymore; I just pipe the logs through jlog (`go install github.com/jrockway/json-logs/cmd/jlog`) and the results are much prettier than what zap would do itself. And it's the same tool I use every day to look at production logs, so there are no surprises. Ultimately, I wrote this because I like small pieces of software that do one thing, and found myself writing the same boilerplate over and over. Overall, this has been a success. I have written small utilities (github.com/jrockway/alertmanager-status, github.com/jrockway/nodedns, github.com/jrockway/alertmanager-discord, which I forked from someone else and converted to use opinionated-server), and larger applications (github.com/jrockway/jsso2, github.com/jrockway/ekglue), and overall not having to write the same monitoring/logging code over and over has proven quite productive. I have not achieved my goal of "1 hour to make a production application" quite yet, but opinionated-server is certainly helping.
- burnthrow 6y agoI so want to believe this is satire. Never change, HN. It's too bad your comment doesn't use the royal "we" like in the project README.
- slow_donkey 6y agoAt least explain why you think it qualifies as satire
- burnthrow 6y agoReply on 0 comment thread with wall-of-text self promotion completely ignoring the submission itself. The "Show HN Takeover", a veteran move.
- go-masonry 6y agoThank for such a long and very detailed comment. I'll try to explain our design goals. I think the main difference is that mortar is heavily based on Dependency Injection (uber.Fx) and this affects a lot. If you want you can have multiple instance of the Logger interface, just 'name' the instance and FX will help you inject the one you like. https://godoc.org/go.uber.org/fx#Annotated https://godoc.org/go.uber.org/fx#Annotated This makes it's extremely easy to inject any instance anywhere you like. What is not mentioned on the main page is the Builder approach we use https://github.com/go-masonry/mortar/blob/master/wiki/builder.md https://github.com/go-masonry/mortar/blob/master/wiki/builde... This allows to partially configure any concrete implementation ahead of time, say in some company-platform library and the developer will only need to "Build" it in the final project or adjust parameters if needed. This project is actually a second version of an internal project we created at Here Mobility, eventually this design helped us to increase developer velocity by introducing - a unified project structure - devops preconfigured libraries to help integrate the services within our internal cloud setup - we had several groups and each had more than one products with backends so we needed to allow every group to develop the way they wanted, but at the same time maintain company standards and devops practices. It's not easy to make sure all your company services are aligned to the same versions unless you work on top of mono repo and we didn't. Mortar design goal was some what a compromise to allow customization but if not a good and reliable standard.
- movedx 6y agoI always try to implement these things and then eventually just end up using Gin and some plugins and building something my self. This often leads to a smaller code footprint and a lower cognitive load when returning to the code 3-6 months later to add some feature.
- domano 6y agoEvery time i tried gin i ended up just using the standard lib handlers and gorilla mux and some middlewares. Am i missing out on something? I've been doing it for years now
- svntid 6y agothis is great!
- brundolf 6y agoI wonder, is there a good even-handed critique out there of the dependency-injection pattern and related philosophies? For me and probably a lot of people here, the pattern fundamentally offends my sensibilities; mainly, but not exclusively, because it forces you to put everything into classes. I'd much rather pass plain functions when I need inversion of control, and use closures if I really need to "configure" some piece of code. I'd also prefer to import and pass down those functions explicitly, instead of having values magically resolve in some runtime context that lives outside of the "real language". I do acknowledge that languages like Java don't, or at least didn't for a long time, support these other language features and so may have had no choice but to use DI as a crutch. But I've also seen it used in languages that do have those features, like JavaScript. On the other hand, I'm not sure I've used the pattern enough to critique it fairly. And obviously it's been used for decades at many very large and successful companies. So what I'd really like is to hear from someone who's experienced with both worlds about the advantages and disadvantages, assuming that both exist.
- anamexis 6y agoI'm curious what you think the fundamental difference is between passing configuration through functions and closures, and passing it with classes. Classes are just fancy closures, after all.
- rraghur 6y agoDiscussion a couple of days around .Net 5 release announcement ended up going into di vs no di.. I'm on mobile but you can get to it from my comment history
- rraghur 6y agoMore ceremony & semantic load of course and you end up with having to define single use abstractions so that you can mock them in tests I've long been in the pro DI camp and used to think that interfaces and brittle tests were unavoidable.. Only after i recently got into FP with f#, I've come to appreciate how i used to conflate good IoC design with DI usage.. I'm pretty sure that I'm not alone