amazon-ecr-containerd-resolver defines ecrAPI as AWS SDK’s subset.

// ecrAPI contains only the ECR APIs that are called by the resolver
// See for the
// full interface from the SDK.
type ecrAPI interface {
	BatchGetImageWithContext(aws.Context, *ecr.BatchGetImageInput, ...request.Option) (*ecr.BatchGetImageOutput, error)
	GetDownloadUrlForLayerWithContext(aws.Context, *ecr.GetDownloadUrlForLayerInput, ...request.Option) (*ecr.GetDownloadUrlForLayerOutput, error)
	BatchCheckLayerAvailabilityWithContext(aws.Context, *ecr.BatchCheckLayerAvailabilityInput, ...request.Option) (*ecr.BatchCheckLayerAvailabilityOutput, error)
	InitiateLayerUpload(*ecr.InitiateLayerUploadInput) (*ecr.InitiateLayerUploadOutput, error)
	UploadLayerPart(*ecr.UploadLayerPartInput) (*ecr.UploadLayerPartOutput, error)
	CompleteLayerUpload(*ecr.CompleteLayerUploadInput) (*ecr.CompleteLayerUploadOutput, error)
	PutImageWithContext(aws.Context, *ecr.PutImageInput, ...request.Option) (*ecr.PutImageOutput, error)

Defining a smaller, limited interface is always nice. It clearly shows what your code needs. However, in interface-first languages such as Java, having a smaller interface means you need to define your own wrapper interface. Go’s interface provides a better alternative that doesn’t add an unnecessary abstraction layer.

In addition to that, small interface is easier to write test doubles. While tools like gomock makes mocking a big interface easier, pre-programmed mocks are cumbersome to maintain. Other test doubles such as fake objects are often useful to make sure your implementation correctly call its collaborators.


I’ve found Dave Cheney have written about this pattern as well.

Let callers define the interface they require

Interfaces declare the behaviour the caller requires not the behaviour the type will provide. Let callers define an interface that describes the behaviour they expect. The interface belongs to them, the consumer, not you.