Skip to content

SDK composition: generated library SDKs must import shared contracts, not embed them #448

Description

@antoinetoussaint-byte

SDK composition: generated library SDKs must import shared contracts, not embed them

Companion to #446 (durable versioned library publishing). #446 gives us the storelibrary@version → go get / pip / npm. This issue is the composition rule that makes multiple library SDKs co-installable in one consumer, which #446 does not address and which the solutions use-case hits immediately.

The problem

A solution consumes several module SDKs at once — e.g. accounts + a future billing + a shared platform/common. If each SDK is generated independently, each embeds its own copy of the shared protos. Then, in Go:

  • The two copies are distinct, non-interchangeable types (accountssdk.Moneybillingsdk.Money).
  • Both register the same proto file path in the global descriptor registry → hard panic at init:
    panic: proto: file "platform/common/v1/common.proto" is already registered.

Python has the equivalent descriptor-pool duplicate-symbol failure; TS duplicates types. So naive multi-library consumption is not just messy — it fails to link. Under #446's "one repo per library, go get" model this bites the first time two SDKs share a first-party proto.

Evidence today: codefly-dev/saas-sdk-go was generated as the whole saas-starter public surface under one prefix (accounts + saas/policy + saas/catalog + common …). Fine as one module's SDK; the moment a second module SDK also carries saas/policy or a shared common, go get-ing both panics.

The fix — composition = dependency resolution (fits #446 natively)

  1. Shared contracts are their own libraries. platform/common, saas/policy, etc. become versioned Library resources published via Epic: durable versioned library publishing — storage abstraction for go get / pip / npm #446 (common-sdk-go, common-sdk wheel, @org/common-sdk).
  2. Module SDK codegen imports deps, does not regenerate them. In buf generate, the shared modules are declared deps and excluded from generation; the module SDK's descriptors reference the shared library's already-generated package path (managed-mode go_package / python root of the dependency, not a local prefix). This is exactly how googleapis / protovalidate are already handled — extend it to first-party shared contracts.
  3. Consumers compose via the package manager. go get accounts-sdk billing-sdk → MVS resolves one common-sdk → types interchangeable, single registration. pip/npm likewise. No bespoke umbrella generator; the subset a solution needs is just the set of libraries it depends on.

What this asks of the library model / CLI

  • library.codefly.yaml (or the export recipe) needs to express inter-library dependencies (this SDK library depends on common-sdk@^2), so codegen can wire imports and the resolver can pin the closure coherently.
  • The codegen step (codefly generate proto / a future codefly generate sdk) needs a "generate local, import deps" mode with a stable mapping from a proto module → its published library's package coordinates.
  • Coherence gate: when resolving a consumer's closure, validate a single major per shared library (no version diamonds) — the composition-layer analog of Epic: durable versioned library publishing — storage abstraction for go get / pip / npm #446's immutability lock.
  • Relevant to Epic: durable versioned library publishing — storage abstraction for go get / pip / npm #446 open question "cross-language version lockstep?": shared-contract libraries almost certainly want lockstep (one common version → all language exports tagged together) so the closure stays coherent across go/python/ts.

Scope / phasing

  • Go + Python first (TS after), matching the solutions need.
  • P0: prove the pattern with saas-sdk-* — factor the shared sub-packages (policy, common) out of the accounts SDK into a shared library, regenerate accounts to import it, and show go get-ing accounts + a second stub SDK no longer panics.
  • Aligns with lodestar#53 item 4 (the solutions SDK wrapper) and the chosen model there: composable subset per solution, git-pinned buf workspace interim store.

Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions