Alat Resolusi Ketergantungan
Penyelesai ketergantungan (selanjutnya disebut sebagai penyelesai, sekitar. Terjemahan). Atau manajer paket adalah program yang mendefinisikan serangkaian modul yang konsisten dengan mempertimbangkan batasan yang ditetapkan oleh pengguna.
Batasan biasanya ditentukan oleh nama modul dan nomor versi. Dalam ekosistem JVM untuk modul Maven, nama organisasi (id grup) juga akan ditunjukkan. Selain itu, pembatasan dapat mencakup rentang versi, modul yang dikecualikan, penggantian versi, dll.
Tiga kategori utama paket diwakili oleh paket OS (Homebrew, paket Debian, dll.),
modul untuk bahasa pemrograman tertentu (CPAN, RubyGem, Maven, dll) dan ekstensi khusus aplikasi (Eclipse plugins, IntelliJ plugins, ekstensi VS Code).
Semantik penyelesaian
Pada perkiraan pertama, kita dapat merepresentasikan dependensi dari modul sebagai DAG (grafik asiklik terarah).
Representasi ini disebut grafik dependensi. Pertimbangkan dependensi dari dua modul:
a:1.0
tergantung pada c:1.0
b:1.0
tergantung pada c:1.0
dan d:1.0
+-----+ +-----+ |a:1.0| |b:1.0| +--+--+ +--+--+ | | +<-------+ | | vv +--+--+ +--+--+ |c:1.0| |d:1.0| +-----+ +-----+
Jika modul bergantung pada a:1.0
dan b:1.0
, maka daftar dependensi lengkap akan disajikan a:1.0
, b:1.0
, c:1.0
dan d:1.0
. Dan ini hanya tur pohon.
Situasi akan menjadi lebih rumit jika dependensi transitif ditentukan oleh berbagai versi:
a:1.0
tergantung pada c:1.0
b:1.0
tergantung pada c:[1.0,2)
dan d:1.0
+-----+ +-----+ |a:1.0| |b:1.0| +--+--+ +--+--+ | | | +-----------+ | | | vvv +--+--+ +--+------+ +--+--+ |c:1.0| |c:[1.0,2)| |d:1.0| +-----+ +---------+ +-----+
Atau, jika versi yang berbeda ditentukan untuk dependensi transitif:
a:1.0
tergantung pada c:1.0
b:1.0
tergantung pada c:1.2
dan d:1.2
Atau, jika ada pengecualian untuk ketergantungan:
- ketergantungan pada
a:1.0
, yang tergantung pada c:1.0
, tidak termasuk c:*
b:1.0
tergantung pada c:1.2
dan d:1.2
Penyelesai yang berbeda mengartikan pembatasan yang ditetapkan oleh pengguna secara berbeda. Saya menyebutnya aturan semantik resolvers.
Anda mungkin perlu mengetahui beberapa dari semantik ini, misalnya:
- semantik modul Anda sendiri (ditentukan oleh alat bantu yang Anda gunakan);
- semantik perpustakaan yang Anda gunakan (ditentukan oleh alat bangun yang digunakan penulis)
- semantik modul yang akan digunakan modul Anda sebagai dependensi (ditentukan oleh alat build pengguna akhir).
Alat Resolusi Ketergantungan dalam Ekosistem JVM
Karena saya mendukung sbt
, saya harus bekerja terutama di ekosistem JVM.
Semantics Maven: terdekat-menang
Dalam grafik di mana ada konflik dependensi (dalam grafik dependensi a
ada banyak versi komponen d
, misalnya d:1.0
dan d:2.0
), Maven menggunakan strategi menang-menang untuk menyelesaikan konflik.
Menyelesaikan konflik dependensi adalah proses yang menentukan versi artefak mana yang akan dipilih jika beberapa versi artefak yang sama ditemukan di antara dependensi. Maven memilih definisi terdekat. Yaitu menggunakan versi yang paling dekat dengan proyek Anda di pohon dependensi.
Anda selalu dapat dijamin untuk menggunakan versi yang benar dengan secara eksplisit mendeklarasikannya dalam POM proyek. Perhatikan bahwa jika dua versi ketergantungan memiliki kedalaman yang sama di pohon, yang pertama akan dipilih. Definisi terdekat berarti bahwa versi yang paling dekat dengan proyek di pohon dependensi akan digunakan. Misalnya, jika dependensi untuk A
, B
dan C
didefinisikan sebagai A -> B -> C -> D 2.0
dan A -> E -> D 1.0
, maka, ketika membangun A
, D 1.0
akan digunakan, karena jalur dari A
ke D
melalui E
lebih pendek (daripada melalui B
dan C
, sekitar terjemahan).
Ini berarti bahwa banyak modul Java yang diterbitkan menggunakan Maven dikompilasi menggunakan semantik nearest-wins
. Untuk menggambarkan hal di atas, buat pom.xml
sederhana:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>foo</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>com.typesafe.play</groupId> <artifactId>play-ws-standalone_2.12</artifactId> <version>1.0.1</version> </dependency> </dependencies> </dependencyManagement> </project>
mvn dependency:build-classpath
mengembalikan mvn dependency:build-classpath
yang diselesaikan.
Perlu dicatat bahwa pohon yang dihasilkan menggunakan com.typesafe:config:1.2.0
meskipun Akka 2.5.3
transitif bergantung pada com.typesafe:config:1.3.1
.
mvn dependency:tree
memberikan konfirmasi visual:
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ foo --- [INFO] com.example:foo:jar:1.0.0 [INFO] \- com.typesafe.play:play-ws-standalone_2.12:jar:1.0.1:compile [INFO] +- org.scala-lang:scala-library:jar:2.12.2:compile [INFO] +- javax.inject:javax.inject:jar:1:compile [INFO] +- com.typesafe:ssl-config-core_2.12:jar:0.2.2:compile [INFO] | +- com.typesafe:config:jar:1.2.0:compile [INFO] | \- org.scala-lang.modules:scala-parser-combinators_2.12:jar:1.0.4:compile [INFO] \- com.typesafe.akka:akka-stream_2.12:jar:2.5.3:compile [INFO] +- com.typesafe.akka:akka-actor_2.12:jar:2.5.3:compile [INFO] | \- org.scala-lang.modules:scala-java8-compat_2.12:jar:0.8.0:compile [INFO] \- org.reactivestreams:reactive-streams:jar:1.0.0:compile
Banyak perpustakaan menyediakan kompatibilitas ke belakang, tetapi kompatibilitas langsung tidak dijamin dengan beberapa pengecualian, yang mengkhawatirkan.
Semantik Apache Ivy: menang-terbaru
Secara default, Apache Ivy menggunakan strategi menang terbaru untuk menyelesaikan konflik ketergantungan.
Jika wadah ini tidak ada, maka manajer konflik default digunakan untuk semua modul. Manajer konflik default saat ini adalah "revisi terbaru".
Catatan: conflicts
kontainer adalah salah satu file Ivy.
Hingga versi SBT 1.3.x
penyelesai dependensi internal adalah Apache Ivy. pom.xml
digunakan sebelumnya dijelaskan dalam SBT sedikit lebih singkat:
ThisBuild / scalaVersion := "2.12.8" ThisBuild / organization := "com.example" ThisBuild / version := "1.0.0-SNAPSHOT" lazy val root = (project in file(".")) .settings( name := "foo", libraryDependencies += "com.typesafe.play" %% "play-ws-standalone" % "1.0.1", )
Di sbt shell
masukkan show externalDependencyClasspath
untuk mendapatkan classpath yang teratasi. Itu harus menunjukkan versi com.typesafe:config:1.3.1
. Selain itu, peringatan berikut masih akan ditampilkan:
[warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings.
Memanggil evicted
dalam sbt shell
memungkinkan Anda untuk mendapatkan laporan resolusi konflik:
sbt:foo> evicted [info] Updating ... [info] Done updating. [info] Here are other dependency conflicts that were resolved: [info] * com.typesafe:config:1.3.1 is selected over 1.2.0 [info] +- com.typesafe.akka:akka-actor_2.12:2.5.3 (depends on 1.3.1) [info] +- com.typesafe:ssl-config-core_2.12:0.2.2 (depends on 1.2.0) [info] * com.typesafe:ssl-config-core_2.12:0.2.2 is selected over 0.2.1 [info] +- com.typesafe.play:play-ws-standalone_2.12:1.0.1 (depends on 0.2.2) [info] +- com.typesafe.akka:akka-stream_2.12:2.5.3 (depends on 0.2.1)
Dalam semantik latest-wins
, menentukan config:1.2.0
dalam praktik berarti "berikan saya versi 1.2.0 atau lebih tinggi."
Perilaku ini sedikit lebih disukai daripada dalam strategi nearest-wins
, karena versi perpustakaan transitif tidak diturunkan peringkatnya. Namun, panggilan yang evicted
harus memeriksa untuk melihat apakah penggantian dilakukan dengan benar.
Semantics Coursier: terbaru-menang
Sebelum kita mendekati deskripsi semantik, saya akan menjawab pertanyaan penting - bagaimana Coursier diucapkan. Menurut catatan Alex Arshambo , itu diucapkan chick-sie .
Menariknya, dokumentasi untuk Coursier memiliki halaman tentang versi , yang berbicara tentang semantik penyelesaian dependensi.
Pertimbangkan persimpangan interval yang diberikan:
- Jika kosong (interval tidak memotong), maka ada konflik.
- Jika tidak ada interval yang ditentukan, diasumsikan bahwa persimpangan diwakili oleh (,) (interval yang sesuai dengan semua versi).
Kemudian, pertimbangkan versi spesifik:
- Kami membuang versi tertentu di bawah batas interval.
- Jika ada versi khusus di atas batas interval, maka ada konflik.
- Jika versi spesifik berada dalam batas-batas interval, hasilnya harus yang terbaru.
- Jika tidak ada versi khusus di dalam atau di atas batas interval, hasilnya harus berupa interval.
Karena dikatakan
, oleh karena itu - ini adalah semantik dari latest-wins
.
Anda dapat memverifikasi ini dengan mengambil sbt 1.3.0-RC3
, yang menggunakan Coursier.
ThisBuild / scalaVersion := "2.12.8" ThisBuild / organization := "com.example" ThisBuild / version := "1.0.0-SNAPSHOT" lazy val root = (project in file(".")) .settings( name := "foo", libraryDependencies += "com.typesafe.play" %% "play-ws-standalone" % "1.0.1", )
Memanggil show externalDependencyClasspath
dari sbt 1.3.0-RC3
console akan mengembalikan com.typesafe:config:1.3.1
, seperti yang diharapkan. Laporan Resolusi Konflik melaporkan hal yang sama:
sbt:foo> evicted [info] Here are other dependency conflicts that were resolved: [info] * com.typesafe:config:1.3.1 is selected over 1.2.0 [info] +- com.typesafe.akka:akka-actor_2.12:2.5.3 (depends on 1.3.1) [info] +- com.typesafe:ssl-config-core_2.12:0.2.2 (depends on 1.2.0) [info] * com.typesafe:ssl-config-core_2.12:0.2.2 is selected over 0.2.1 [info] +- com.typesafe.play:play-ws-standalone_2.12:1.0.1 (depends on 0.2.2) [info] +- com.typesafe.akka:akka-stream_2.12:2.5.3 (depends on 0.2.1)
Catatan: Apache Ivy mengemulasi semantik nearest-wins
?
Ketika menyelesaikan dependensi modul dari repositori Maven, Ivy mengkonversi file POM
dan menempatkan atribut force="true"
di ivy.xml
di cache.
Sebagai contoh, cat ~/.ivy2/cache/com.typesafe.akka/akka-actor_2.12/ivy-2.5.3.xml
:
<dependencies> <dependency org="org.scala-lang" name="scala-library" rev="2.12.2" force="true" conf="compile->compile(*),master(compile);runtime->runtime(*)"/> <dependency org="com.typesafe" name="config" rev="1.3.1" force="true" conf="compile->compile(*),master(compile);runtime->runtime(*)"/> <dependency org="org.scala-lang.modules" name="scala-java8-compat_2.12" rev="0.8.0" force="true" conf="compile->compile(*),master(compile);runtime->runtime(*)"/> </dependencies>
Dokumentasi Ivy mengatakan:
Dua manajer konflik latest
mempertimbangkan atribut dependensi force
.
Dengan demikian, dependensi langsung dapat mendeklarasikan atribut force
(lihat dependensi), menunjukkan bahwa dependensi langsung dan revisi tidak langsung, preferensi harus diberikan kepada revisi dependensi langsung.
Bagi saya, formulasi ini berarti bahwa force="true"
dikandung untuk mendefinisikan kembali logika latest-wins
dan meniru semantik nearest-wins
. Tapi, untungnya, ini tidak ditakdirkan untuk terjadi, dan kami sekarang memiliki latest-wins
: seperti yang kita lihat, sbt 1.2.8
mengambil com.typesafe:config:1.3.1
.
Namun, orang dapat mengamati efek force="true"
saat menggunakan manajer konflik yang ketat, yang tampaknya rusak.
ThisBuild / conflictManager := ConflictManager.strict
Masalahnya adalah bahwa manajer konflik yang ketat tampaknya tidak mencegah penggantian versi. show externalDependencyClasspath
senang hati mengembalikan com.typesafe:config:1.3.1
.
Masalah terkait adalah bahwa menambahkan versi com.typesafe:config:1.3.1
, yang dimasukkan oleh manajer konflik yang ketat dalam grafik, menyebabkan kesalahan.
ThisBuild / scalaVersion := "2.12.8" ThisBuild / organization := "com.example" ThisBuild / version := "1.0.0-SNAPSHOT" ThisBuild / conflictManager := ConflictManager.strict lazy val root = (project in file(".")) .settings( name := "foo", libraryDependencies ++= List( "com.typesafe.play" %% "play-ws-standalone" % "1.0.1", "com.typesafe" % "config" % "1.3.1", ) )
Ini terlihat seperti ini:
sbt:foo> show externalDependencyClasspath [info] Updating ... [error] com.typesafe#config;1.2.0 (needed by [com.typesafe#ssl-config-core_2.12;0.2.2]) conflicts with com.typesafe#config;1.3.1 (needed by [com.example#foo_2.12;1.0.0-SNAPSHOT]) [error] org.apache.ivy.plugins.conflict.StrictConflictException: com.typesafe#config;1.2.0 (needed by [com.typesafe#ssl-config-core_2.12;0.2.2]) conflicts with com.typesafe#config;1.3.1 (needed by [com.example#foo_2.12;1.0.0-SNAPSHOT])
Tentang versi
Kami menyebutkan semantik latest-wins
, menunjukkan bahwa versi dalam representasi string dapat muncul dalam beberapa urutan.
Oleh karena itu, versi adalah bagian dari semantik.
Prosedur Pembuatan Versi di Apache Ivy
Komentar Javadoc ini mengatakan bahwa saat membuat komparator versi, Ivy berfokus pada fungsi membandingkan versi dari PHP :
Fungsi ini pertama menggantikan _, - dan + dengan sebuah titik .
dalam representasi string versi dan juga menambahkan .
sebelum dan sesudah segala sesuatu yang bukan angka. Jadi, misalnya, '4.3.2RC1' menjadi '4.3.2.RC.1'. Dia kemudian membandingkan bagian-bagian yang diterima dari kiri ke kanan.
Untuk bagian yang mengandung elemen khusus ( dev
, alpha
atau a
, beta
atau b
, RC
atau rc
, #
, pl
atau p
) *, elemen tersebut dibandingkan dalam urutan berikut:
string apa pun yang bukan elemen khusus <dev <alpha = a <beta = b <RC = rc <# <pl = p.
Dengan demikian, tidak hanya level yang berbeda (misalnya, '4.1' dan '4.1.2') dapat dibandingkan, tetapi versi khusus PHP yang berisi informasi tentang status pengembangan.
* sekitar. perev.
Kita dapat memeriksa bagaimana versi dipesan dengan menulis fungsi kecil.
scala> :paste
Prosedur Pembuatan Versi dalam Coursier
Halaman GitHub pada semantik resolusi dependensi memiliki bagian tentang versi.
Coursier menggunakan urutan versi Maven yang disesuaikan. Sebelum membandingkan, representasi string dari versi dipecah menjadi elemen yang terpisah ...
Untuk mendapatkan elemen-elemen tersebut, versi dipisahkan oleh karakter., -, dan _ (dan pemisah itu sendiri dibuang), dan dengan penggantian huruf-ke-angka atau penggantian-ke-huruf.
Untuk menulis tes, buat subproyek dengan libraryDependencies += "io.get-coursier" %% "coursier-core" % "2.0.0-RC2-6"
dan jalankan console
:
sbt:foo> helper/console [info] Starting scala interpreter... Welcome to Scala 2.12.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_212). Type in expressions for evaluation. Or try :help. scala> import coursier.core.Version import coursier.core.Version scala> def sortVersionsCoursier(versions: String*): List[String] = | versions.toList.map(Version.apply).sorted.map(_.repr) sortVersionsCoursier: (versions: String*)List[String] scala> sortVersionsCoursier("1.0", "2.0", "1.0-alpha", "1.0+alpha", "1.0-X1", "1.0a", "2.0.2") res0: List[String] = List(1.0-alpha, 1.0, 1.0-X1, 1.0+alpha, 1.0a, 2.0, 2.0.2)
Ternyata, Coursier memesan nomor versi dalam urutan yang sama sekali berbeda dari Ivy.
Jika Anda menggunakan tag alfabet permisif, maka pemesanan ini dapat menyebabkan kebingungan.
Tentang rentang versi
Biasanya, saya menghindari menggunakan rentang versi, meskipun mereka banyak digunakan di webjars dan modul npm yang diterbitkan ulang di Maven Central. Sesuatu seperti "is-number": "^4.0.0"
dapat ditulis dalam modul "is-number": "^4.0.0"
yang akan sesuai dengan [4.0.0,5)
.
Penanganan rentang versi di Apache Ivy
Dalam perakitan ini, angular-boostrap:0.14.2
tergantung pada angular:[1.3.0,)
.
ThisBuild / scalaVersion := "2.12.8" ThisBuild / organization := "com.example" ThisBuild / version := "1.0.0-SNAPSHOT" lazy val root = (project in file(".")) .settings( name := "foo", libraryDependencies ++= List( "org.webjars.bower" % "angular" % "1.4.7", "org.webjars.bower" % "angular-bootstrap" % "0.14.2", ) )
Memanggil show externalDependencyClasspath
di sbt 1.2.8
akan mengembalikan angular-bootstrap:0.14.2
dan angular:1.7.8
. Dan kemana 1.7.8
pergi? Ketika Ivy menemukan berbagai versi, ia pada dasarnya pergi ke Internet dan menemukan apa yang dapat ia temukan, kadang-kadang bahkan menggunakan screencraping.
Pemrosesan rentang versi ini membuat rakitan tidak berulang (menjalankan rakitan yang sama setiap beberapa bulan sekali memberi Anda hasil yang berbeda).
Menangani rentang versi dalam Coursier
Bagian resolusi ketergantungan Coursier di halaman github
berbunyi:
Versi spesifik pada interval lebih disukai
Jika modul Anda memiliki ketergantungan pada [1.0,2.0) dan 1.4, persetujuan versi akan dilakukan untuk 1.4.
Jika ada ketergantungan pada 1.4, maka versi ini akan lebih disukai di kisaran [1.0,2.0).
Itu terlihat menjanjikan.
sbt:foo> show externalDependencyClasspath [warn] There may be incompatibilities among your library dependencies; run 'evicted' to see detailed eviction warnings. [info] * Attributed(/Users/eed3si9n/.sbt/boot/scala-2.12.8/lib/scala-library.jar) [info] * Attributed(/Users/eed3si9n/.coursier/cache/v1/https/repo1.maven.org/maven2/org/webjars/bower/angular/1.4.7/angular-1.4.7.jar) [info] * Attributed(/Users/eed3si9n/.coursier/cache/v1/https/repo1.maven.org/maven2/org/webjars/bower/angular-bootstrap/0.14.2/angular-bootstrap-0.14.2.jar)
show externalDependencyClasspath
pada rakitan yang sama dengan angular-bootstrap:0.14.2
mengembalikan angular-bootstrap:0.14.2
dan angular:1.4.7
seperti yang diharapkan. Ini merupakan peningkatan dari Ivy.
Pertimbangkan kasus yang lebih rumit ketika beberapa rentang versi disjoint digunakan. Sebagai contoh:
ThisBuild / scalaVersion := "2.12.8" ThisBuild / organization := "com.example" ThisBuild / version := "1.0.0-SNAPSHOT" lazy val root = (project in file(".")) .settings( name := "foo", libraryDependencies ++= List( "org.webjars.npm" % "randomatic" % "1.1.7", "org.webjars.npm" % "is-odd" % "2.0.0", ) )
Memanggil show externalDependencyClasspath
di sbt 1.3.0-RC3
mengembalikan kesalahan berikut:
sbt:foo> show externalDependencyClasspath [info] Updating https://repo1.maven.org/maven2/org/webjars/npm/kind-of/maven-metadata.xml No new update since 2018-03-10 06:32:27 https://repo1.maven.org/maven2/org/webjars/npm/is-number/maven-metadata.xml No new update since 2018-03-09 15:25:26 https://repo1.maven.org/maven2/org/webjars/npm/is-buffer/maven-metadata.xml No new update since 2018-08-17 14:21:46 [info] Resolved dependencies [error] lmcoursier.internal.shaded.coursier.error.ResolutionError$ConflictingDependencies: Conflicting dependencies: [error] org.webjars.npm:is-number:[3.0.0,4):default(compile) [error] org.webjars.npm:is-number:[4.0.0,5):default(compile) [error] at lmcoursier.internal.shaded.coursier.Resolve$.validate(Resolve.scala:394) [error] at lmcoursier.internal.shaded.coursier.Resolve.validate0$1(Resolve.scala:140) [error] at lmcoursier.internal.shaded.coursier.Resolve.$anonfun$ioWithConflicts0$4(Resolve.scala:184) [error] at lmcoursier.internal.shaded.coursier.util.Task$.$anonfun$flatMap$2(Task.scala:14) [error] at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:307) [error] at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41) [error] at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64) [error] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [error] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [error] at java.lang.Thread.run(Thread.java:748) [error] (update) lmcoursier.internal.shaded.coursier.error.ResolutionError$ConflictingDependencies: Conflicting dependencies: [error] org.webjars.npm:is-number:[3.0.0,4):default(compile) [error] org.webjars.npm:is-number:[4.0.0,5):default(compile)
Secara teknis, itu benar, karena rentang ini tidak tumpang tindih. Sementara sbt 1.2.8
menyelesaikan ini ke is-number:4.0.0
.
Karena rentang versi cukup umum untuk mengganggu, saya mengirimkan Permintaan Tarik kepada Coursier untuk menerapkan aturan semantik latest-wins
yang memungkinkan Anda untuk memilih versi yang lebih baru dari batas bawah rentang.
Lihat kurir / kurir # 1284 .
Kesimpulan
Semantik resolver mendefinisikan classpath spesifik berdasarkan batasan yang ditentukan pengguna.
Biasanya, perbedaan dalam detail dimanifestasikan dalam berbagai cara untuk menyelesaikan konflik versi.
- Maven menggunakan strategi
nearest-wins
, yang dapat menurunkan ketergantungan transitif. - Ivy menggunakan strategi
latest-wins
. - Coursier terutama menggunakan strategi win-win
latest-wins
, sembari mencoba menentukan versi dengan lebih ketat. - Penangan rentang versi Ivy pergi ke Internet, yang membuat build yang sama tidak dapat diulang.
- Coursier dan Ivy mengatur representasi string versi dengan cara yang sangat berbeda.
Bahkan seluk beluk ekosistem Scala seperti itu tidak akan dibahas di ScalaConf pada 26 November di Moskow. Artem Seleznev akan memperkenalkan praktik bekerja dengan database dalam pemrograman fungsional tanpa JDBC. Wojtek Pitula akan berbicara tentang integrasi dan memberi tahu bagaimana dia membuat aplikasi tempat dia meletakkan semua perpustakaan yang berfungsi. Dan 16 laporan penuh dengan hardcore teknis akan disajikan pada konferensi tersebut.