This is an automated email from the ASF dual-hosted git repository.
etudenhoefner pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new a3aff95f9e Docs: Mention how to add new functionality without breaking
APIs (#8293)
a3aff95f9e is described below
commit a3aff95f9e60962240b94242e24a778760bdd1d9
Author: Eduard Tudenhoefner <[email protected]>
AuthorDate: Mon Aug 14 09:26:33 2023 +0200
Docs: Mention how to add new functionality without breaking APIs (#8293)
---
CONTRIBUTING.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4bf9881edd..e160aaec17 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -108,6 +108,85 @@ Example:
void sequenceNumber(long sequenceNumber);
```
+## Adding new functionality without breaking APIs
+Ideally, we'd want to add new functionality without breaking existing APIs,
especially within the scope of the API modules that are being checked by
[Revapi](https://revapi.org/).
+
+Let's assume we'd want to add a `createBranch(String name)` method to the
`ManageSnapshots` API.
+
+The most straight-forward way would be to add the below code:
+
+```java
+public interface ManageSnapshots extends PendingUpdate<Snapshot> {
+ // existing code...
+
+ // adding this method introduces an API-breaking change
+ ManageSnapshots createBranch(String name);
+}
+```
+
+And then add the implementation:
+```java
+public class SnapshotManager implements ManageSnapshots {
+ // existing code...
+
+ @Override
+ public ManageSnapshots createBranch(String name, long snapshotId) {
+ updateSnapshotReferencesOperation().createBranch(name, snapshotId);
+ return this;
+ }
+}
+```
+
+### Checking for API breakages
+Running `./gradlew revapi` will flag this as an API-breaking change:
+```
+./gradlew revapi
+> Task :iceberg-api:revapi FAILED
+> Task :iceberg-api:showDeprecationRulesOnRevApiFailure FAILED
+
+1: Task failed with an exception.
+-----------
+* What went wrong:
+Execution failed for task ':iceberg-api:revapi'.
+> There were Java public API/ABI breaks reported by revapi:
+
+ java.method.addedToInterface: Method was added to an interface.
+
+ old: <none>
+ new: method org.apache.iceberg.ManageSnapshots
org.apache.iceberg.ManageSnapshots::createBranch(java.lang.String)
+
+ SOURCE: BREAKING, BINARY: NON_BREAKING, SEMANTIC: POTENTIALLY_BREAKING
+
+ From old archive: <none>
+ From new archive: iceberg-api-1.4.0-SNAPSHOT.jar
+
+ If this is an acceptable break that will not harm your users, you can ignore
it in future runs like so for:
+
+ * Just this break:
+ ./gradlew :iceberg-api:revapiAcceptBreak --justification "{why this
break is ok}" \
+ --code "java.method.addedToInterface" \
+ --new "method org.apache.iceberg.ManageSnapshots
org.apache.iceberg.ManageSnapshots::createBranch(java.lang.String)"
+ * All breaks in this project:
+ ./gradlew :iceberg-api:revapiAcceptAllBreaks --justification "{why
this break is ok}"
+ * All breaks in all projects:
+ ./gradlew revapiAcceptAllBreaks --justification "{why this break is
ok}"
+
----------------------------------------------------------------------------------------------------
+
+```
+
+### Adding a default implementation
+In order to avoid breaking the API, we can add a default implementation that
throws an `UnsupportedOperationException`:
+```java
+public interface ManageSnapshots extends PendingUpdate<Snapshot> {
+ // existing code...
+
+ // introduces new code without breaking the API
+ default ManageSnapshots createBranch(String name) {
+ throw new UnsupportedOperationException(this.getClass().getName() + "
doesn't implement createBranch(String)");
+ }
+}
+```
+
## Style
For Java styling, check out the section