forked from fl00r/go-tarantool-1.6
-
Notifications
You must be signed in to change notification settings - Fork 60
Open
Description
Overview
We are considering a significant design change for the upcoming v3 release of the Tarantool Go client: replacing the current concrete Future struct with an interface. This change aims to improve testability, enable better abstraction, and support more flexible usage patterns—while carefully preserving performance.
This issue is a great opportunity for interns to understand Go interface design, API evolution, and trade-offs between abstraction and performance.
Current State
Right now, tarantool.Future is defined as a concrete struct with internal fields and public methods:
type Future struct {
// unexported fields
}It exposes the following public methods:
// NewFuture creates a new empty Future for a given Request.
func NewFuture(req Request) *Future
// WaitChan returns a channel that closes when the response arrives or an error occurs.
func (fut *Future) WaitChan() <-chan struct{}
// GetTyped waits for the Future and decodes the response directly into 'result' using msgpack.
// This avoids intermediate []interface{} allocation and is faster than Get().
func (fut *Future) GetTyped(result interface{}) error
// Get waits for the Future and returns data as []interface{} + error.
// Slower than GetTyped due to generic decoding.
func (fut *Future) Get() ([]interface{}, error)
// GetResponse returns the raw Response and error.
func (fut *Future) GetResponse() (Response, error)
// These methods are used internally by the client to fulfill the Future:
func (fut *Future) SetError(err error)
func (fut *Future) SetResponse(header Header, body io.Reader) errorProblem Statement
- Hard to mock in tests: Because
Futureis a concrete type with internal state and private fields, it’s difficult to create lightweight mocks or stubs for unit testing code that depends on it. - Leaky abstraction: Public setters like
SetErrorandSetResponseexpose internal mechanics that users shouldn’t need to interact with—these are really meant for internal use by the connection logic. - Tight coupling: Code that consumes a
*Futureis tightly coupled to this specific implementation, limiting flexibility (e.g., you can’t easily substitute a fake or instrumented future).
Proposed Change (for v3)
Refactor Future into an interface that captures only the observable behavior users need:
type Future interface {
WaitChan() <-chan struct{}
Get() ([]interface{}, error)
GetTyped(result interface{}) error
GetResponse() (Response, error)
}- Remove
SetErrorandSetResponsefrom the public API (they become internal implementation details). - Keep the existing struct (possibly renamed to
futureordefaultFuture) as the default implementation of this interface. - Update
NewFuture(or introduce a new factory) to return the interface:func NewFuture(req Request) Future
Benefits
- Testability: Users (and our own tests) can easily create mock
Futureimplementations that return predefined responses or simulate errors—without spinning up a real connection or dealing with channels/mutexes. - Cleaner API: Hides internal mutation methods (
Set*) that were never meant for public use. - Extensibility: Enables alternative
Futureimplementations (e.g., for observability, caching, or async backends) without breaking user code.
Concerns & Considerations
- Performance: Adding an interface indirection might introduce a tiny overhead. However:
- Modern Go compilers optimize interface calls well.
- The cost is likely negligible compared to network I/O and msgpack decoding.
- We should benchmark before/after (interns: this is a great task!).
- Naming: Should the interface be called
Future, and the concrete typefuture(unexported)? - Go way: It may be agains go way "call by interfaces, return structs".
Tasks
- Draft the new
Futureinterface. - Refactor the existing struct into an unexported implementation.
- Update all internal usage to work with the interface.
- Write unit tests that mock the
Futureinterface. - Benchmark
Get(),GetTyped(), andWaitChan()before/after to verify no significant regression (pprof, go test -cpuprofile, -memprofile, go benches). - Update documentation and examples.
Resources
Metadata
Metadata
Assignees
Labels
No labels