Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Construisez votre bibliothèque Java/Kotlin

Construisez votre bibliothèque Java/Kotlin

mbonnin

June 11, 2024
Tweet

More Decks by mbonnin

Other Decks in Programming

Transcript

  1. Quelques chiffres •> 500k bibliothèques dans Maven Central (44TB)¹ •>

    1 000 000 000 000 requêtes/an •190 bibliothèques dans nowinandroid² •39 dans le spring graphql initializr³ 1.https://mvnrepository.com/repos/central 2.https://github.com/android/nowinandroid 3.https://start.spring.io/
  2. query GetUser { user { name address } } Apollo

    Kotlin* * https://github.com/apollographql/apollo-kotlin class User( val name: String, val address: String )
  3. LA SURFACE D’API MINIMISER •apollo-compiler •apollo-runtime •apollo-normalized-cache •apollo-mockserver •apollo-tooling •apollo-cli

    •apollo-testing-support •apollo-mpp-utils •😬 •apollo-compiler •apollo-runtime •apollo-normalized-cache •apollo-mockserver •apollo-cli
  4. LA CHARGE MENTALE MINIMISER “There should be one-- and preferably

    only one --obvious way to do it.” — Zen of Pyhton
  5. plugins { id("org.jetbrains.kotlin.jvm") } buildscript { dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") }

    } dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") } Ajouter un plugin Gradle? plugins { id("org.jetbrains.kotlin.jvm").version("2.0.0").apply(false) } plugins { alias(“libs.plugins.kotlin") }
  6. LA PRÉCÉDENCE ÉVITER •Gradle properties •Themes Android •Apollo headers •…

    val apolloClient = ApolloClient.Builder() .addHttpHeader("conference", “Android Makers”) .build() apolloClient.query(myQuery) .addHttpHeader("conference", "Devfest Lille") .httpHeaders(emptyList()) .execute() fun <D: Query.Data> ApolloClient.myQuery(query: Query<D>): ApolloCall<D> { return query(query).addHttpHeader("conference", "Devfest Lille") }
  7. FAVORISER LA COMPOSITION fun MockServer.enqueueString(response: String) {…} fun <D: Query.Data>

    MockServer.enqueueResponse(response: ApolloResponse<D>) {…} fun MockServer.enqueueString(response: String) {…} fun <D:Query.Data> ApolloResponse<D>.serialize(): String {}
  8. ÉCRIRE UNE API CHAINABLE val request = ApolloRequest.Builder(query) .httpHeader( "conference",

    "Devfest Lille” ) .fetchPolicy(CacheOnly) .build() apolloClient.execute(query) apolloClient.query(query) .httpHeader( "conference", "Devfest Lille” ) .fetchPolicy(CacheOnly) .execute()
  9. GÉRER LES ERREURS •Checked exceptions? •Unchecked exceptions? •Sealed classes? •Result?

    •Untagged unions (KT-68296) •… https://elizarov.medium.com/kotlin-and-exceptions-8062f589d07
  10. CHOISIR SON PUBLIC Java friendly Large Audience 30 ans d

    ’ experience @JvmName @JvmStatic @JvmOverloads Kotlin friendly Builder DSL Extensions Coroutines Compose Union types?
  11. 1x 1x 1x 1x 1x 1x 1x 1x MINIMALE TESTABLE

    PRÉVISIBLE TYPÉE DOCUMENTÉE COMPOSABLE COHÉRENTE CHAINABLE
  12. Maven Central . └── com └── example └── coffee ├──

    0.0.1 │ ├── coffee-0.0.1.jar │ ├── coffee-0.0.1.module │ └── coffee-0.0.1.pom └── maven-metadata-local.xml https://repo1.maven.org/maven2/ com.example:coffee:0.0.1 artifact group version
  13. Maven != Maven Central •Maven = build tool •Maven Repository

    Layout = format¹ •Maven Central = repository •Sonatype 1.https://maven.apache.org/repository/layout.html
  14. Maven Central in 2011* •4GB RAM •2x Xeon 1.6 GHz

    •286GB repo size •12B requêtes * https://www.sonatype.com/blog/2011/07/central-grows-up-see-the-history
  15. Maven Central •Gratuit •Immuable •Véri fi cations •Domaine •Licence, description

    •Signatures & checksums •Sources et javadoc •Potentiellement vides
  16. Publication •Maven Central •Top pour les utilisateurs •Moins top pour

    les contributeurs •Maven •sonatype/central-publishing-maven-plugin¹ •Gradle •vanniktech/gradle-maven-publish-plugin² •or just “vanniktech ’ s” 1.https://central.sonatype.org/publish/publish-maven/ 2.https://github.com/vanniktech/gradle-maven-publish-plugin
  17. SOURCE INCOMPATIBILITÉ 1/3 public class CoffeeMachine { public Drink brew()

    throws OutOfCoffeeException { return new Coffee(); } }
  18. TERMINOLOGIE Compatibilité source •à la compilation • Ne pas casser

    Compatibilité binaire •à l ’ execution • Ne VRAIMENT pas casser
  19. SOURCE & BINAIRE WHY NOT BOTH? public class CoffeeMachine {

    public Drink brew(){ return new Coffee(); } }
  20. SOURCE & BINAIRE WHY NOT BOTH? public class CoffeeMachine {

    public Drink brew(){ return new Coffee(); } }
  21. L’illusion de la compatibilité source 100% import coffee.*; import tea.*;

    public class Main { public static void main(String[] args) { Machine teaMachine = new Machine(); //... } }
  22. COMPORTEMENTALE INCOMPATIBILITÉ 3/3 xkcd: work fl ow public class CoffeeMachine

    { public Coffee brew() { return new Coffee(“Peru"); } }
  23. COMPORTEMENTALE INCOMPATIBILITÉ 3/3 xkcd: work fl ow public class CoffeeMachine

    { public Coffee brew() { return new Coffee("Colombia"); } }
  24. 0.x.y 1.0.0 1.x.y 2.0.0-alpha.x 2.0.0-beta.x 2.0.0-rc.x 2.0.0 2.0.1 • •

    • • • • • • •“J ’ ai tout cassé hier soir” •Bugs •Tests •Feature complete •Documentation •API stable •Migration guide •Battle tested •Long term support Documentez la stabilité de vos alpha Utilisez les alphas en production
  25. “Breaking changes are broken” — Rich Hickey¹ 1.https://www.youtube.com/watch?v=oyLBGkS5ICk •Enlever du

    code produit des incompatibilités •Il suf fi t de ne jamais enlever (!) •A la place, changer le namespace •Renommer le groupId en com.example.lib2 •Renommer le package en lib2
  26. private static final String[] KNOWN_UNSTABLE_API_ANNOTATIONS = { "org.jetbrains.annotations.ApiStatus.ScheduledForRemoval", "org.jetbrains.annotations.ApiStatus.Experimental", "org.jetbrains.annotations.ApiStatus.Internal",

    "com.google.common.annotations.Beta", "io.reactivex.annotations.Beta", "io.reactivex.annotations.Experimental", "rx.annotations.Experimental", "rx.annotations.Beta", "org.apache.http.annotation.Beta", "org.gradle.api.Incubating" }; @Incubating ANNOTATIONS @Incubating interface GradleLifecycle { @Incubating open fun beforeProject(action: IsolatedAction<in Project?>?) } StaticAnalysisAnnotationManager.java
  27. @Deprecated ANNOTATIONS public class ApolloClient implements Closeable { @Deprecated(since =

    "4.0") void stop() { close(); } @Override public void close() {} }
  28. @Deprecated ANNOTATIONS class ApolloClient : Closeable { @Deprecated("Use close() instead",

    ReplaceWith("close()")) fun stop() {close()} override fun close() {} }
  29. @Deprecated ANNOTATIONS class ApolloClient : Closeable { @Deprecated("Use close() instead",

    level = DeprecationLevel.ERROR) fun stop() {close()} override fun close() {} }
  30. @Deprecated ANNOTATIONS class ApolloClient : Closeable { @Deprecated("Use close() instead",

    level = DeprecationLevel.HIDDEN) fun stop() {close()} override fun close() {} }
  31. S ’ assurer qu ’ on ne casse pas l

    ’ API de manière accidentelle: •japicmp (jar)¹ •binary-compatibility-validator (kotlin)² •metalava (java + kotlin)³ API/ABI validation 1. https://github.com/siom79/japicmp 2. https://github.com/Kotlin/binary-compatibility-validator 3. https://android.googlesource.com/platform/tools/metalava/
  32. METALAVA ./gradlew metalavaGenerateSignature // Signature format: 4.0 package coffee {

    public class Coffee implements coffee.Drink {} public class CoffeeMachine { ctor public CoffeeMachine(); method public coffee.Drink! brew(); } public interface Drink {} }
  33. public class CoffeeMachine { public Drink brew() throws OutOfCoffeeException {

    return new Coffee(); } } lib/build/metalava/current.txt:9: error: Method coffee.CoffeeMachine.brew added thrown exception coffee.OutOfCoffeeException [ChangedThrows] Aborting: Found compatibility problems checking the public API (lib/build/metalava/current.txt) against the API in lib/api.txt ./gradlew metalavaCheckCompatibility
  34. public class CoffeeMachine { public Coffee brew(){ return new Coffee();

    } } lib/build/metalava/current.txt:9: error: Method coffee.CoffeeMachine.brew has changed return type from coffee.Drink to coffee.Coffee [ChangedType] Aborting: Found compatibility problems checking the public API (lib/build/metalava/current.txt) against the API in lib/api.txt ./gradlew metalavaCheckCompatibility
  35. •Minimisez! •Utilisez les guidelines •Dogfood! •Remerciez MavenCentral (meme si des

    fois…) •Gérez votre compatibilité binaire: •semver •@OptIn/@Deprecated •metalava & friends CONCLUSION