3 ms·
I really think the library search is more of something you inherit from other languages, though database drivers are something you need to go looking for. The
by jrockway 2y ago
I really think the library search is more of something you inherit from other languages, though database drivers are something you need to go looking for. The standard library has an adequate HTTP router (though I prefer grpc-gateway as it autogenerates docs, types, etc.) and logger (slog, but honestly plain log is fine).
For your database driver, just use pgx. For migrations, tern is fine. For the tiniest bit of sugar around scanning database results into structs, use sqlx instead of database/sql.
I wouldn't recommend using a testing framework in Go: https://go.dev/wiki/TestComments#assert-libraries https://go.dev/wiki/TestComments#assert-libraries
Here's how I do dependency injection:
func main() {
foo := &Foo{
Parameter: goesHere,
}
bar := &Bar{
SomethingItNeeds: canJustBeTypedIn,
}
app := &App{
Foo: foo,
Bar: bar,
}
app.ListenAndServe()
}
If you need more complexity, you can add more complexity. I like "zap" over "slog" for logging. I am interested in some of the DI frameworks (dig), but it's never been a clear win to me over a little bit of hand-rolled complexity like the above.
A lot of people want some sort of mocking framework. I just do this:
- func foo(x SomethingConcrete) {
- x.Whatever()
- }
+ interface Whateverer { Whatever() }
+ func foo(x Whateverer) {
+ x.Whatever()
+ }
Then in the tests:
type testWhateverer {
n int
}
var _ Whateverer = (*testWhateverer)(nil)
func (w *testWhateverer) Whatever() { w.n++ }
func TestFoo(t *testing.T) {
x := &testWhateverer{}
foo(x)
if got, want := x.n, 1; got != want {
t.Errorf("expected Whatever to have been called: invocation count:\n got: %v\n want: %v", got, want)
}
}
It's easy. I typed it in an HN comment in like 30 seconds. Whether or not a test that counts how many times you called Whatever is up to you, but if you need it, you need it, and it's easy to do.
- peterldowns 2y agoI've been writing Golang for years now, and I heavily endorse everything written here. Only exception is you should use my migration library [0] instead of tern — you don't need down migrations, and you can stop worrying about migration number conflicts. One other suggestion I'll make is you probably at some point should write a translation layer between your API endpoints and the http.Handler interface, so that your endpoints return `(result *T, error)` and your tests can avoid worrying about serde/typeasserting the results. [0] https://github.com/peterldowns/pgmigrate https://github.com/peterldowns/pgmigrate
- jrockway 2y agoI can definitely get behind using some other migration library! Thank you for writing and sharing this!
- peterldowns 2y agoThanks :) if you have the time, I’d sincerely appreciate feedback on it and especially on its docs/readme, even a simple github issue for “this is confusing” or “this typo is weird” would be really helpful.
- domano 2y agoBold of you to flat out drop down migrations. I guess having a new up migration to cover the case is better, but its nice to have a documented way of rolling back (which would be the down migration) - without applying it programmatically. But it helps if other team members can see how a change should be rolled back ideally.
- glenjamin 2y agoThe key here is that in production it's almost always not safe to actually apply a down migration - so it's better to make that clear than to pretend there's some way to reverse an irreversible operation.
- peterldowns 2y agoGlenjamin gave a great answer. I’ll just add that in my experience (being responsible for the team’s database, at a few companies over the years), down migrations are NEVER helpful when a migration goes wrong. Roll-forward is the only correct model. Besides, there are plenty of migrations that cant be safely rolled back, so most “down migrations” either destroy data or don’t work (or both.)
- glenjamin 2y agoThis looks excellent! The go tools for managing DB schema migrations have always felt lacking to me, and it seems like your tool ticks all of the boxes I had. Except for one: lack of support for CREATE INDEX CONCURRENTLY (usually done by detecting that and skipping the transaction for that migration). How do you handle creating indexes without this?