Go Cloud separates cloud service use from provider setup
Go Cloud launches with portable service APIs for AWS and GCP. Its blob example shows how familiar Go interfaces separate application operations from provider construction.
Portability becomes expensive when provider-specific calls appear throughout an application's business logic. Replacing the storage service then means changing more than the code that opens a connection. Today's Go Cloud announcement proposes a shared set of APIs for common cloud services, with provider differences concentrated in their setup and implementation. The useful question is how much application code can operate on the shared contract.
Eno Compton and Cassandra Salisbury describe the project as a response to interviews with teams building cloud applications in Go. Those teams wanted to move workloads between providers or operate across cloud and local environments. Building a private abstraction competes with immediate feature work. Go Cloud offers a common implementation of that abstraction so every application need not begin the same work independently.
I find the approach consistent with what I value in Go. An application should express the operation it needs through a comprehensible dependency. The value of an abstraction depends on whether that dependency remains meaningful when its implementation changes.
The launch defines a limited set of services
Go Cloud launches with blob storage, MySQL access, and runtime configuration. It also includes an HTTP server configured for request logging, tracing, and health checks. The announced provider support covers AWS and Google Cloud Platform. These are the initial capabilities, rather than a claim that the project already abstracts every cloud service.
This scope gives the portability argument something concrete to evaluate. A program that writes objects needs a storage API whose operations map to its intended providers. A program depending on a provider-specific service outside the announced set still needs another arrangement. Portability applies to the dependencies represented by the available contracts.
Choosing that contract is a tradeoff for an application author. A shared operation can preserve deployment choices, while a provider-specific operation may express a requirement that the shared API does not cover. The useful boundary follows the application's requirements. A design gains little by calling itself portable while quietly depending on behavior available only through one implementation. Keeping those dependencies visible allows an author to decide which parts of the program can actually move.
Even within a shared contract, deployment involves provider-specific choices. Credentials and resource names do not disappear because a program receives a common Go type. The launch article's storage example shows these choices explicitly in separate setup functions. That visible separation is more useful than pretending that selecting another provider requires no provider knowledge.
A bucket is constructed before application code uses it
The article opens an AWS S3 bucket using an AWS session and s3blob.OpenBucket. Its alternative setup obtains GCP credentials and an HTTP client before calling gcsblob.OpenBucket. Both functions return *blob.Bucket. Application code receiving the bucket can then use the same write operations.
This division resembles ordinary dependency passing. The application does not construct a provider client every time it writes an object. Its setup supplies a bucket, and the operation works with that value. Provider selection remains in code whose job is to establish dependencies, which reduces the number of places that require a change.
The portable part can be illustrated with the calls shown in the launch article. This fragment assumes b is an initialized bucket and ctx is the caller's context.
w, err := b.NewWriter(ctx, "report.txt", nil)
if err != nil {
return fmt.Errorf("open report writer: %v", err)
}
_, writeErr := w.Write([]byte("ready\n"))
closeErr := w.Close()
if writeErr != nil {
return fmt.Errorf("write report: %v", writeErr)
}
if closeErr != nil {
return fmt.Errorf("close report writer: %v", closeErr)
}
The code preserves the write failure if one occurs and still closes the writer. On a successful write, a close failure becomes the returned error. The launch example specifically checks Close, so treating it as unimportant cleanup would omit part of the demonstrated operation's success criteria.
The fragment uses error formatting available in the Go version of this period. It deliberately leaves bucket construction outside the operation. Importing a provider SDK into this fragment would weaken the separation the example is meant to demonstrate.
Familiar interfaces reduce adaptation work
The launch article emphasizes compatibility with io.Reader, io.Writer, and *sql.DB. The Go 1.10 io definitions give those byte-stream contracts their existing meaning. A writer accepts bytes and reports how many it wrote along with an error. A reader supplies bytes under the corresponding read contract.
Using those interfaces means existing code can participate without learning a provider-specific streaming model. A serializer that already writes to io.Writer can target a blob writer. Its task remains producing bytes. The bucket implementation handles the provider operation beneath that contract.
The distinction also constrains what the interface promises. io.Writer describes byte acceptance and errors. It does not by itself specify a cloud provider's authorization policy or object naming restrictions. Those requirements need to be understood at the service API and setup layers. Reusing an interface is useful because it has an established contract, not because it describes every property of the remote service.
I prefer that kind of reuse to an application framework requiring its own equivalent of every standard interface. It leaves ordinary Go code usable outside the cloud integration. The shared contract is small enough to explain without reconstructing the provider's entire client library.
Construction remains part of the design
The announcement also introduces Wire as a way to generate provider-specific setup code. It observes that higher-level dependencies are built from lower-level ones. Generating the wiring addresses the repetitive constructor calls without moving provider-specific operations into every application function.
This does not eliminate configuration responsibility. The generated setup still needs inputs appropriate to the chosen provider. A portable application can have different startup code in different deployments while sharing the logic that uses a bucket. That is the specific form of portability demonstrated by the launch.
For another provider or an on-premise service, the project invites implementations of its driver interfaces. That makes extension an implementation task against the shared API. The important engineering question for such an implementation is whether the operations preserve the contract the application relies on. A bucket consumer should continue to handle writes and completion through the same calls, while provider setup supplies the credentials and concrete driver for its deployment.