Yup.
Somewhat formalized: https://peter.bourgon.org/blog/2018/08/22/observability-sign...
HN user
e1b1ce94085da63902d7c51d8f1e0697
Yup.
Somewhat formalized: https://peter.bourgon.org/blog/2018/08/22/observability-sign...
This is definitely not true (shrug) Attrition has more than doubled and most engineering teams are critically understaffed.
Most do, though, because there does tend to be a more correct position on this class of stuff.
A handful of people left
They lost close to half of their engineering team, significant percentages of their operations teams, and many senior leaders.
No, I don't know anyone at Basecamp.
I'm not sure why you think people who care about justice harass coworkers as a matter of course. It's strictly the opposite, in my (considerable) experience.
I’m not sure why you think people who care about justice draw that energy from the same pool that motivates their work, as if it’s a zero-sum calculus. It isn’t. People who think about things systemically tend to apply that perspective in all of their practices. And that’s the value.
My point is that DEBUG level logging is (hopefully!) not on by default, and that this is what it makes the production log volume manageable.
My experience has been that 1 customer-facing byte tends to generate something like ~10 DEBUG-level telemetry bytes. This level of request amplification can't be feasibly sustained at nontrivial request volumes, your logging infrastructure would dwarf your production infrastructure.
Yes. All of the "private" correspondence he's received, which of course he cannot share, has been overwhelmingly positive. Indeed.
Hah! OK, sure.
I am currently at an organisation where Splunk is a bottleneck.
Only at trivial request volume. It’s easy to overwhelm any logging system without discipline.
Log after is SOP in any reasonably large architecture. Too much logging is a problem you encounter very quickly with nontrivial traffic.
Logging is significantly cheaper than tracing to maintain in a usable state.
You only emit a single log event (line) per request. Problem solved.
Yes, it’s useful, but that occasional usefulness is substantially outweighed by it’s perpetual cost.
Nope, this produces unmanageable log volume.
When I read MVC I guess I take it pretty literally, in that you would have packages named model/s, view/s, and controller/s. That's what I'm speaking to. Of course, abstractly, many programs tend to be structured in some kind of layering scheme that's MVC-ish :)
You continue to assert that error handling adds noise to a block of code. This is an opinion, that's fine, but it's not an objective truth. In many many domains -- in fact the domains which Go targets -- error handling is equally important to business logic.
That is not at all what those links indicate.
Wow! No wonder you find this tedious.
Your gateway struct hopefully looks something like
type Fooer interface{ ... }
type Barer interface{ ... }
type gateway struct{ f Fooer; b Barer; ... }
newGateway(f Fooer, b Barer, ...) (*gateway, error) { ... }
func (g *gateway) StartTransaction(...)
That is, a gateway is something that depends on other things, modeled as interfaces, and provides capabilities as methods. +--------------------+
| gateway |
| - f Fooer |
| - b Barer |
| - ... |
| |
input -------> StartTransaction -----> output
| ... |
+--------------------+
When you want to exercise this code, you want to construct an instance with mock/deterministic dependencies, so that you have predictable results when you apply input and receive output. That's the model: give input, assert output.But your linked code is kind of different! Each subtest varies not the input but the behavior of the mocked dependencies. I understand the point: you want to run through all the codepaths in the gateway method. But is that worth testing? Do the tests meaningfully reduce risk? I dunno. It's not obvious to me that they do.
The use of gomock is also a big smell. Generating mocks kind of defeats the purpose of using them. I would definitely write a bespoke client:
type mockClient struct {
StartTransaction func(...) (xxx.Transaction, error)
Upsert func(...) (xxx.Result, error)
AbortTransavtion func(...) (xxx.Xxx, error)
}
Then each test case is simpler to express as for _, tc := range []struct{
name string
client *mockClient
input UpdateConfigRequest
startRes xxx.Transaction
startErr error
upsertRes xxx.Result
upsertErr error
res xxx.UpdateConfigResponse
err error
} {
{
name: "success",
client: &mockClient{StartTransaction: good, ...},
startRes: ...,
upsertRes: ...,
res: ...,
},
{
name: "bad start",
client: &mockClient{StartTransaction: bad, ...},
startErr: ...,
},
{
name: "bad upsert",
client: &mockClient{StartTransaction: good, Upsert: bad, ...},
startRes: ...,
upsertErr: ...,
},
...
} {
t.Run(tc.name, func(t *testing.T) {
g := newGateway{tc.client}
output := g.StartTransaction(tc.input)
// asserts
})
}CRDTs and smart contracts are almost totally unrelated to each other.
Is there any theoretical groundwork happening on how CRDTs can preserve domain semantics?
Effectively using CRDTs requires a fundamentally different model of state. Typical CRUD stuff simply doesn't work. If you're unwilling or unable to refactor your state layer, then CRDTs are gonna seem totally infeasible.
I don't understand why you would reply with this comment. If the question is "how do I do X?" and X isn't something that you should be doing, why is it not constructive to point that out?
When something is unsound it is valuable to point it out. It prevents harm.
I'm sorry if you've had bad experiences with this approach in the past, but it emphatically does not lead to low quality and/or meaningless tests. It's the essential foundation of well-abstracted and maintainable software.
This isn't in the relevant domain.
"MVC" is not an idiomatic pattern in Go.
Maintainable software projects are modeled as a dependency graph of components that encapsulate implementation details and depend on other components.
func main
foo, err := NewFoo()
handle err
bar, err := NewBar(foo)
handle err
Given a single component, each external dependency should be injected as an interface. type Fooer interface{ Foo() int }
func NewBar(f Fooer) (*Bar, error) {
...
}
Test components in isolation by providing mock (stub, fake, whatever, it's all meaningless) implementations of its dependencies. func TestBar(t *testing.T) {
f := &mockFoo{...}
b, err := NewBar(f)
...Because when the author writes or speaks about the project, they consistently demonstrate a fundamental lack of understanding of the domain. There is a reason that GunDB is never mentioned in literature or papers.