Classifying Schema Changes: Adding Methods

Question

Is adding a method to a schema considered a major or minor change?

major = breaking
minor = non-breaking

Answer

The answer is both yes and no, depending on the type of schema:
wrapper schema = no
interface schema = yes

Example 1: Wrapper Schema

revision = 1

type Module {
  method(arg: String): UInt32!
}

version = 2

type Module {
  method(arg: String): UInt32!
  another(arg: String): UInt32!
}

Users of revision 1 can safely use revision 2 without any needed code changes. Users considered:

  • apps
  • plugins
  • wrappers

Users that would be potentially broken by this change:

  • interfaces IF they were allowed to use types from non-interface schemas.
  • source-code verifiers IF they had to re-fetch schema 2 as a dynamic import & generate bindings before being able to verify source-code.

Example 2: Interface Schema

revision = 1

type Module {
  method(arg: String): UInt32!
}

version = 2

type Module {
  method(arg: String): UInt32!
  another(arg: String): UInt32!
}

user

#import { Module } into Interface from "...dyn-uri..."

type Module implements Interface_Module { }

As you can see, the user schema above will be “broken” when revision 2 is published. This is because it will no longer fully implement the interface it claims to.

NOTE: this is not a problem if a static URI is being used for interfaces.

Closing Thoughts

This makes me think that we should create 2 new rules around interface schemas:

  1. All interface schema dependencies are “locked” and cannot be changed over-time. This is effectly already taking place, since all dependent types are embedded in the interface schema already.
  2. When wrapper import an interface, a static URI (locked dep) should be embedded into the schema, instead of the dynamic one. This is so that users of the wrapper can safely see what “version” of the interface the wrapper implements, without having to introspect types themselves.

For example:

1. interface = `interface.eth@1` -> `ipfs/QmHASH`
2. wrapper = import & implement `interface.eth`
3. build wrapper = wrapper implements `ipfs/QmHASH` **NOT** `interface.eth`
4. app dev = wrapper.implements("interface.eth") -> true
5. upgrade interface = `interface.eth@2` -> `ipfs/QmNEW_HASH`
6. app dev = wrapper.implements("interface.eth") -> false

NOTE: If the registry is being used, a fully-qualified version URI pointing to a package on the registry would be sufficiently static, and IPFS wouldn’t be stored.

1 Like