API design guidelines give ambiguous advice about coping with old versions
Submitted by Simon McVittie
Assigned to Philip Withnall
Link to original bug (#106862)
Description
https://dbus.freedesktop.org/doc/dbus-api-design.html#api-versioning says:
New API can be added to a D-Bus interface without incrementing the version number, as such additions are still backwards-compatible. However, clients should gracefully handle the org.freedesktop.DBus.Error.UnknownMethod error reply from all D-Bus method calls if they want to run against older versions of the service which don’t implement new methods. (This also prevents use of generated bindings; any method which a client wants to gracefully fall back from should be called using a generic D-Bus method invocation rather than a specific generated binding.)
I'm not sure why it says that? Code generated by, for example, gdbus-codegen or (ugh) dbus-binding-tool from a local copy of some API v1.23 should interoperate fine with a service implementing some API v1.0 (assume semantic versioning here). The generated methods will raise UnknownMethod as usual.
The uses of generated bindings that would be a problem are those where the generated binding is external to the client, or where the binding is generated from interface definitions that are external to the client. Philip, are those situations the ones you had in mind? I think we should clarify this.
Let's say a client calls methods on a service that implements an API, and ideally it wants to call DoThingsWithOptions() (introduced in com.example.Something1 v1.23), falling back to DoThings() if DoThingsWithOptions() isn't implemented. Assume that service v1.x implements com.example.Something1 v1.x, and assume GDBus (the same considerations would apply to dbus-glib or QtDBus, with appropriate relabelling). There are several ways the client could call the method:
(A) Service ships a library containing gdbus-codegen-generated bindings for its version of com.example.Something1. Client calls methods from that library.
- Client needs to be compiled against library v1.23 or later
- Client has a strong dependency on library v1.23 or later at
runtime, unless statically linked (if the library is not present,
the client won't even start)
- Client has a weak dependency on service v1.0 or later at runtime
(if the service is not present, the relevant feature won't work
but the client will still run)
(B) Service ships com.example.Something1.xml in /usr/share/dbus-1/interfaces. Client runs gdbus-codegen to generate private bindings which are built into the client.
- Client needs to be compiled against library v1.23 or later
- Client only needs service v1.0 or later at runtime
(C) Client ships a private copy of com.example.Something1.xml and uses it to generate private bindings which are built into the client.
- No build-time dependency
- Client has a weak dependency on service v1.0 or later at runtime
(D) Client uses g_dbus_connection_call() or equivalent to call at least DoThingsWithOptions(), and perhaps both methods.
- No build-time dependency
- Client has a weak dependency on service v1.0 or later at runtime
For tightly-coupled components where it's OK for a new client to get a hard dependency on a new service, like xdg-desktop-portal-gtk depending on xdg-desktop-portal, cases (A) or (B) are fine.
For loosely-coupled components where graceful fallbacks and backwards compatibility are needed, I would personally recommend (C) if the interface is of a significant size, or (D) if the interface is small and simple.
Note that it is not OK to expose the generated bindings in case (B) as part of a library, because their API would change unpredictably, depending on the precise version of the service that the client is compiled against. It would be OK to expose the generated bindings in case (C) as part of a library.
Version: git master