Had a concrete idea on tweak the Go SDK dx while working on updates to Function.
It would remove the main func from user code and feels overall simpler.
Example user code for 3 functions in a module named "MyModule" (name specified in dagger.json, so codegen can always read it without needing to compile anything):
func (mod *MyModule) Binary(ctx context.Context) (*File, error) {
return dag.Go().Build(...)
}
func (mod *MyModule) IntegTests(ctx context.Context) error {
return dag.Go().Test(...)
}
func (mod *MyModule) Publish(ctx context.Context, version string) error {
// ...
}
Basic idea is that codegen creates an empty struct type MyModule struct {} and the user just adds more methods to that struct.
Underneath the hood, the implementation would be updated so that the real main func is entirely in generated code and does the same reflection/ast magic that it does today, but tweaked to look at methods on the module object rather than on funcs you pass to dag.Module().WithFunction(...).
- The SDK would internally still use a
withFunctionapi, that just doesn't leak to user-code anymore. Same idea as we already follow in the Python SDK basically. - I think this would actually simplify the internal Go SDK code too in that we can remove the ugly hack where the user-facing codegen'd
WithFunctionis forced to accept an arg of typeanyso it can handle arbitraryfuncs.
I think this would also give us some really simple+intuitive routes to supporting the other ideas we had for the longer term, e.g.:
// If we add Check/Artifact/etc. in the future, it's just another return type
func (mod *MyModule) IntegTests(ctx context.Context) (*Check, error) {
return dag.Go().Test(...)
}
// Support for user-defined custom chainable objects is pretty intuitive too
type MyCustomObject struct {
Foo string
}
func (mod *MyModule) CustomObject(ctx context.Context, foo string) (*MyCustomObject, error) {
return &MyCustomObject{Foo: foo}, nil
}
func (obj *MyCustomObject) SayFoo(ctx context.Context) string {
return fmt.Sprintf("I SAY %s", obj.Foo)
}
// Extending existing types from core or other modules also becomes obvious (if we choose to support it)
func (dir *Directory) NetlifyDeploy(ctx context.Context, token *Secret) error {
// ...
}
The only slight downside I can think of is that after dagger init the user needs to start from blank and know to type func (mod *MyModule) ... but, besides docs, I think that could be helped by maybe just also generating a "starter file" separate from dagger.gen.go that has dummy method on *MyModule written out, with a comment explaining how to add more.
- Then later we can of course layer on a whole system of "init templates" that we've talked about previously