chrome-devtools-kotlin
chrome-devtools-kotlin copied to clipboard
Use a DSL instead of data classes for input types to improve binary compatibility
Currently all input/output types for domain commands are data classes. While these are easy to declare on library side, and use on the consumer side, they can cause a bunch of compatibility issues when they evolve.
Because of the frequent updates to the protocol, and frequent addition/removal of experimental properties in the tip-of-tree version used by chrome-devtools-kotlin, it would be useful to replace these data classes by nice DSLs that would be backwards compatible and force users to name the properties.
Another (minor) problem caused by this is that experimental types or properties in the input generate opt-in warnings for the data class constructor. We can't mark the whole constructor as experimental (that would be an unnecessary pain for the user), while we could mark individual builder properties as experimental.
Some stats to inform the decision. Here is the diff between 2020-01-10 and 2022-08-20:
{
"commandStats": {
"inputParams": {
"added": {
"mandatoryNonXp": 0,
"mandatoryXp": 0,
"optionalNonXp": 29,
"optionalXp": 37
},
"removed": {
"mandatoryNonXp": 0,
"mandatoryXp": 0,
"optionalNonXp": 3,
"optionalXp": 1
},
"changed": {
"mandatoryToOptional": 10,
"optionalToMandatoryNonXp": 0,
"optionalToMandatoryXp": 0,
"noLongerExperimental": 0,
"becameExperimental": 1
},
"zeroToSomeParams": 9,
"someToZeroParams": 0,
"zeroToSomeOptional": 21,
"someToZeroOptional": 0
},
"outputParams": ... // omitted for brevity
},
"eventStats": ... // omitted for brevity
}
Things to note:
- mandatory-optional changes
- no mandatory parameters were ever added or removed from the command inputs
- no optional parameter ever became mandatory
- some mandatory parameters became optional (but not many: 10 in >2.5 years)
- having no params / some params
- some commands had 0 params, and then had some (but not many: 9 in >2.5 years)
- no command ever went from having some parameters to having no parameters
- having no optional params / some optional params
- 21 commands had 0 optional params, and then had some optional params (non-negligible)
- no command ever went from having some optional parameters to having no optional parameters
Point 1 means it should be ok-ish to have mandatory parameters as constructor args of the builder, and as parameters of command methods. When a mandatory param becomes optional, the signature will change and break binary compatibility, but it doesn't happen very often so it's a fair price to pay because it allows to have compile-time safety on mandatory parameters, which is a very critical thing to have.
Point 2 means that, for commands that currently have no parameters, it's ok to not create an input type nor a method taking this input type. We can simply create a no-arg command method. Later on, when parameters are added (they should only be optional, given the stats), we can add an input type with builder, a command method that takes the input type, and a command method that takes only a builder lambda argument. We need to generate JVM overloads of that method with default {}
lambda arg to ensure backwards compat in that case - just don't forget @JvmOverloads
there and we should be fine.
Point 3 means that we should always have an overload of the command method without the optional params lambda. This is because if a command didn't have optional params, we generate a method without the lambda (we can even avoid generating a builder type at all). Later, when params are added, we need to keep binary compat with callers that used the method without lambda. This can be achieved by using @JvmOverloads
for the method with lambda with default {}
, as mentioned in the previous point.
Based on the details above, here is what I think we should do.
-
Commands with 0 parameters:
- No input class, no builder
- Command method with no arguments
-
Commands with 0 optional parameters (and at least 1 mandatory parameter)
- Input class with no builder
- Command method with mandatory arguments, but no lambda builder argument
- Command method with input class argument (with no default value)
-
Commands with at least 1 optional parameter (with or without mandatory params)
- Input class with builder
- Command method with mandatory parameters + a lambda builder parameter with default value
{}
+@JvmOverloads
to make sure we still have the overload without builder argument - Command method with input class argument (with no default value)