From 7a5c3f2a60b9c8420c0dccc27c85132e9a311157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 10 Jul 2024 15:32:46 +0200 Subject: [PATCH 1/5] ci(github): Run the tests in CI --- .github/workflows/CI.yaml | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/CI.yaml diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml new file mode 100644 index 0000000..f970ceb --- /dev/null +++ b/.github/workflows/CI.yaml @@ -0,0 +1,47 @@ +name: CI + +# Run this workflow every time a new commit pushed to your repository +on: push + +jobs: + build-and-test: + name: Build and test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Build + run: ./gradlew build + + - name: Upload Unit Test Results + if: always() + uses: actions/upload-artifact@v2 + with: + name: tests-results # Name artifact for storage in cache + path: | + **/build/test-results/**/*.xml + + publish-test-results: + name: Publish tests results + runs-on: ubuntu-latest + needs: build-and-test + # the build-and-test job might be skipped, we don't need to run this job then + if: success() || failure() + permissions: + checks: write + pull-requests: write + + steps: + - name: Download Artifacts + uses: actions/download-artifact@v2 + with: + name: tests-results # Name of artifact in cache + path: tests-results/ + + - name: Publish Unit Test Results + uses: docker://ghcr.io/enricomi/publish-unit-test-result-action:v1 + if: always() + with: + github_token: ${{ github.token }} + files: tests-results/**/*.xml From 284facbcbb225970d4309441dcd87e8c02d557a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 10 Jul 2024 16:05:21 +0200 Subject: [PATCH 2/5] build: Generate a documentation website --- .../Documentation_website.xml | 31 +++++++ .../Open_documentation_website.xml | 7 ++ docs/website/.gitignore | 1 + docs/website/docs/index.md | 7 ++ docs/website/mkdocs.yml | 88 +++++++++++++++++++ docs/website/overrides/home.html | 66 ++++++++++++++ 6 files changed, 200 insertions(+) create mode 100644 .idea/runConfigurations/Documentation_website.xml create mode 100644 .idea/runConfigurations/Open_documentation_website.xml create mode 100644 docs/website/.gitignore create mode 100644 docs/website/docs/index.md create mode 100644 docs/website/mkdocs.yml create mode 100644 docs/website/overrides/home.html diff --git a/.idea/runConfigurations/Documentation_website.xml b/.idea/runConfigurations/Documentation_website.xml new file mode 100644 index 0000000..6f668cf --- /dev/null +++ b/.idea/runConfigurations/Documentation_website.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/.idea/runConfigurations/Open_documentation_website.xml b/.idea/runConfigurations/Open_documentation_website.xml new file mode 100644 index 0000000..e3ad82e --- /dev/null +++ b/.idea/runConfigurations/Open_documentation_website.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/docs/website/.gitignore b/docs/website/.gitignore new file mode 100644 index 0000000..16d3c4d --- /dev/null +++ b/docs/website/.gitignore @@ -0,0 +1 @@ +.cache diff --git a/docs/website/docs/index.md b/docs/website/docs/index.md new file mode 100644 index 0000000..f741e32 --- /dev/null +++ b/docs/website/docs/index.md @@ -0,0 +1,7 @@ +--- +template: home.html +--- + +# Welcome! + +KtMongo is the future of MongoDB. diff --git a/docs/website/mkdocs.yml b/docs/website/mkdocs.yml new file mode 100644 index 0000000..fa4a524 --- /dev/null +++ b/docs/website/mkdocs.yml @@ -0,0 +1,88 @@ +site_name: KtMongo +site_author: 4SH & contributors +site_description: > + Towards the future of MongoDB in Kotlin. +repo_url: https://github.com/4sh/ktmongo +repo_name: 4SH / KtMongo + +theme: + name: material + custom_dir: overrides + features: + - announce.dismiss + - content.action.edit + - content.action.view + - content.code.annotate + - content.code.copy + - content.code.select + # - content.footnote.tooltips + - content.tabs.link + - content.tooltips + # - header.autohide + # - navigation.expand + - navigation.footer + - navigation.indexes + # - navigation.instant + # - navigation.instant.prefetch + # - navigation.instant.progress + # - navigation.prune + - navigation.sections + - navigation.tabs + # - navigation.tabs.sticky + - navigation.top + - navigation.tracking + - search.highlight + - search.share + - search.suggest + - toc.follow + # - toc.integrate + icon: + edit: material/pencil + view: material/eye + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: "System theme (click to switch to light)" + - media: "(prefers-color-scheme: light)" + scheme: default + primary: green + accent: cyan + toggle: + icon: material/brightness-7 + name: "Light theme (click to switch to dark)" + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: green + accent: cyan + toggle: + icon: material/brightness-4 + name: "Dark theme (click to switch to system)" + +plugins: + - social + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.tabbed: + alternate_style: true + +edit_uri: edit/main/docs/website/docs + +use_directory_urls: false + +nav: + - Home: index.md + + - Getting started: [ ] + + - Migrating from KMongo: [ ] diff --git a/docs/website/overrides/home.html b/docs/website/overrides/home.html new file mode 100644 index 0000000..b161425 --- /dev/null +++ b/docs/website/overrides/home.html @@ -0,0 +1,66 @@ +{% extends "main.html" %} +{% block tabs %} +{{ super() }} + + +
+
+
+
+

{{ config.site_name }}

+ +

{{ config.site_description }}

+ +
+
+ + + What is it? + + + + Visit reference + + + + Visit repository + +
+
+
+
+{% endblock %} + +{% block content %} +
+ {{ super() }} +
+{% endblock %} From 9bb039f62f3cdfe6ab6e58e4cb0e1239ab31ce44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Wed, 10 Jul 2024 16:28:58 +0200 Subject: [PATCH 3/5] build: Create the 'library' convention plugin --- build.gradle.kts | 6 ++++++ driver-coroutines/build.gradle.kts | 4 +--- driver-sync/build.gradle.kts | 4 +--- dsl/build.gradle.kts | 4 +--- gradle/conventions/build.gradle.kts | 8 ++++++-- gradle/conventions/src/main/kotlin/library.gradle.kts | 7 +++++++ gradle/libs.versions.toml | 5 ++++- 7 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 gradle/conventions/src/main/kotlin/library.gradle.kts diff --git a/build.gradle.kts b/build.gradle.kts index 7f009f5..7c35530 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,3 +3,9 @@ plugins { alias(libs.plugins.kotlin) apply false alias(libs.plugins.dokkatoo) } + +dependencies { + dokkatoo(projects.dsl) + dokkatoo(projects.driverSync) + dokkatoo(projects.driverCoroutines) +} diff --git a/driver-coroutines/build.gradle.kts b/driver-coroutines/build.gradle.kts index deedb11..dda9a80 100644 --- a/driver-coroutines/build.gradle.kts +++ b/driver-coroutines/build.gradle.kts @@ -1,8 +1,6 @@ plugins { id("conventions.base") - - alias(libs.plugins.kotlin) - alias(libs.plugins.dokkatoo) + id("conventions.library") } dependencies { diff --git a/driver-sync/build.gradle.kts b/driver-sync/build.gradle.kts index 323ff7c..d4f8ebf 100644 --- a/driver-sync/build.gradle.kts +++ b/driver-sync/build.gradle.kts @@ -1,8 +1,6 @@ plugins { id("conventions.base") - - alias(libs.plugins.kotlin) - alias(libs.plugins.dokkatoo) + id("conventions.library") } dependencies { diff --git a/dsl/build.gradle.kts b/dsl/build.gradle.kts index a56edcf..3f982fe 100644 --- a/dsl/build.gradle.kts +++ b/dsl/build.gradle.kts @@ -1,8 +1,6 @@ plugins { id("conventions.base") - - alias(libs.plugins.kotlin) - alias(libs.plugins.dokkatoo) + id("conventions.library") } dependencies { diff --git a/gradle/conventions/build.gradle.kts b/gradle/conventions/build.gradle.kts index e764714..116db11 100644 --- a/gradle/conventions/build.gradle.kts +++ b/gradle/conventions/build.gradle.kts @@ -1,8 +1,12 @@ - plugins { `kotlin-dsl` } kotlin { - jvmToolchain(8) + jvmToolchain(11) +} + +dependencies { + implementation(libs.gradle.kotlin) + implementation(libs.gradle.dokkatoo) } diff --git a/gradle/conventions/src/main/kotlin/library.gradle.kts b/gradle/conventions/src/main/kotlin/library.gradle.kts new file mode 100644 index 0000000..6d9e069 --- /dev/null +++ b/gradle/conventions/src/main/kotlin/library.gradle.kts @@ -0,0 +1,7 @@ +package conventions + +plugins { + kotlin("jvm") + id("dev.adamko.dokkatoo-html") + id("dev.adamko.dokkatoo-gfm") +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba42a42..605cb0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ kotest = "5.9.1" [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -dokkatoo = { id = "dev.adamko.dokkatoo-html", version.ref = "dokkatoo" } +dokkatoo = { id = "dev.adamko.dokkatoo", version.ref = "dokkatoo" } [libraries] kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } @@ -21,6 +21,9 @@ kotest-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kote kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } +gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +gradle-dokkatoo = { module = "dev.adamko.dokkatoo:dokkatoo-plugin", version.ref = "dokkatoo" } + [bundles] kotest = [ "kotest-junit5", From 9f277f95c6fa419f09195eb1ac7ec181c97de055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Fri, 12 Jul 2024 23:06:24 +0200 Subject: [PATCH 4/5] docs(website): Migrate the guides to the website --- docs/README.md | 20 +-- docs/website/docs/guides/overview.md | 67 ++++++++++ docs/{ => website/docs}/guides/search.md | 12 +- .../docs/guides/setup.md} | 14 +- docs/website/docs/index.md | 40 +++++- .../migrate-from-kmongo/nested-fields.md | 16 ++- .../docs/migrate-from-kmongo/search.md} | 36 +++--- .../website/docs/migrate-from-kmongo/setup.md | 9 ++ docs/website/docs/migrate-from-kmongo/why.md | 122 ++++++++++++++++++ docs/website/mkdocs.yml | 17 ++- docs/website/overrides/home.html | 18 +-- 11 files changed, 302 insertions(+), 69 deletions(-) create mode 100644 docs/website/docs/guides/overview.md rename docs/{ => website/docs}/guides/search.md (79%) rename docs/{guides/connect.md => website/docs/guides/setup.md} (71%) rename docs/{ => website/docs}/migrate-from-kmongo/nested-fields.md (70%) rename docs/{migrate-from-kmongo/dsl.md => website/docs/migrate-from-kmongo/search.md} (75%) create mode 100644 docs/website/docs/migrate-from-kmongo/setup.md create mode 100644 docs/website/docs/migrate-from-kmongo/why.md diff --git a/docs/README.md b/docs/README.md index a747e4c..979b430 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,25 +2,13 @@ To learn what KtMongo is and why it exists, see the [root README](../README.md). -To learn about specific KtMongo features, functions or operators, see the Reference. +## The website -## Getting started +The KtMongo website contains guides to get started with KtMongo, guides to migrate from KMongo to KtMongo, and the generated API reference. -To learn how to use KtMongo in your project and to learn about new MongoDB features. +Its sources are available in the [website/](website) directory. -- Adding KtMongo to your project -- [Connecting and configuring a database](guides/connect.md) -- Saving data -- [Searching for data](guides/search.md) -- Referencing nested fields - -## How to migrate from KMongo? - -List of differences between KMongo and KtMongo to help you migrate your existing projects. - -- Using KMongo and KtMongo together -- [Referencing nested fields](migrate-from-kmongo/nested-fields.md) -- [The operator DSL](migrate-from-kmongo/dsl.md) +To build it locally, run the `Open documentation website` run configuration. ## Design documents diff --git a/docs/website/docs/guides/overview.md b/docs/website/docs/guides/overview.md new file mode 100644 index 0000000..2845222 --- /dev/null +++ b/docs/website/docs/guides/overview.md @@ -0,0 +1,67 @@ +# Features overview + +This page describes the improvements brought by KtMongo over the official drivers. +If you're interested in a comparison with KMongo, see [the dedicated page](../migrate-from-kmongo/why.md). + +## Query DSL and optional filters + +A typical scenario is to filter data based on criteria passed by the user. In this example, we'll be searching for users of a given last name, with an optional minimum age filter. + +First, let's declare our classes to represent the data we're working with. The drivers will automatically deserialize documents to these classes. + +```kotlin +class User( + val _id: String, + val name: UserName, + val age: Int, +) + +class UserName( + val firstName: String, + val lastName: String, +) +``` + +Let's implement this request with the official drivers. + +```java title="With the official Java driver" +Bson filter = eq("name.lastName", lastName); + +if(minAge !=null) +filter = + +and(filter, gt("age", minAge)); + + users. + +find(filter); +``` + +The official drivers use the builder pattern. In this example, reference mutability is used to incrementally create the filter. + +This example is hard to maintain: + +- Field names, and their hierarchy, are passed as strings: when refactoring code, requests risk being outdated. +- Filters are of the type `Bson`: the type doesn't help us verify that the request actually applies to the contents of the collection. If we accidentally use a function made for another collection, its filters may apply in different ways or not apply at all if the field names are different. +- There are no type checks: we could try to compare the `name` field with a `String`, which would give no results since it is a child document. +- The structure of the request is not immediately visible at a glance, due to being spread over the condition. + +Now, let's rewrite this example with KtMongo: + +```kotlin title="With KtMongo" +users.find { + User::name / UserName::lastName eq lastName + User::age gtNotNull minAge +} +``` + +A few improvements have been made to simplify the request: + +- Referring to fields is done by writing a reference to the actual fields, meaning we can find all requests using a field by clicking on it in our IDE. +- Refactoring fields will correctly update all requests using them. +- Operator arguments are type-checked, so we cannot compare a field with an incompatible type. +- The structure of the request is immediately visible, and the `$and` operator is implied by the presence of multiple arguments. +- The `gtNotNull` operator handles the optional filter for us. +- Only filters that apply to the collection being searched in can be specified in the `find` block, we cannot accidentally use filters meant for another collection. + +These various improvements make the final request much easier to read and understand at a glance. diff --git a/docs/guides/search.md b/docs/website/docs/guides/search.md similarity index 79% rename from docs/guides/search.md rename to docs/website/docs/guides/search.md index 766afcf..65e56a8 100644 --- a/docs/guides/search.md +++ b/docs/website/docs/guides/search.md @@ -1,11 +1,10 @@ -# Searching the database +# Find data -In this document, we will see how to search for specific data in the database. - -> Pre-requisites: -> - [Obtain a collection](connect.md) +!!! note "" +Before retrieving data, you must [connect to the database and obtain a collection](setup.md). Let's assume we have the following class: + ```kotlin class User( val name: String, @@ -14,6 +13,7 @@ class User( ``` To return all documents in a collection, we can use `find`: + ```kotlin collection.find() .toList() @@ -21,6 +21,7 @@ collection.find() ``` To only return specific documents, we can add a filter expression: + ```kotlin // Find all users who have a name and who are older than 18 collection.find { @@ -31,6 +32,7 @@ collection.find { ``` If we know that only one user may exist, we can use `findOne` instead: + ```kotlin collection.findOne { User::name eq "Sylvain De La Fontaine" } ``` diff --git a/docs/guides/connect.md b/docs/website/docs/guides/setup.md similarity index 71% rename from docs/guides/connect.md rename to docs/website/docs/guides/setup.md index 1cec63b..e4f1c49 100644 --- a/docs/guides/connect.md +++ b/docs/website/docs/guides/setup.md @@ -1,10 +1,17 @@ -# Interacting with a database +# Quick start -This article describes how to connect to a MongoDB instance, how to access a specific collection, and how to perform a simple request. +## Choose between suspension and blocking + +## Add the dependency + +[//]: # (TODO: publish to central) + +KtMongo is currently not published to MavenCentral. To import it into your projects, use a [Gradle composite build](https://docs.gradle.org/current/userguide/composite_builds.html). ## Connect to a database First, instantiate a client and request a specific database: + ```kotlin val database = MongoClient.create("mongodb://localhost:PORT-NUMBER") .getDatabase("foo") @@ -15,6 +22,7 @@ To configure more options, see [the official documentation](https://www.mongodb. ## Access a collection First, create a class that represents the documents stored in the collection: + ```kotlin class User( val name: String, @@ -23,6 +31,7 @@ class User( ``` Now, instantiate the collection: + ```kotlin val collection = database.getCollection("users") .asKtMongo() @@ -31,6 +40,7 @@ val collection = database.getCollection("users") ## Perform a simple operation Now that we have access to the collection, we can perform operations on it: + ```kotlin val count = collection.countDocumentsEstimated() ``` diff --git a/docs/website/docs/index.md b/docs/website/docs/index.md index f741e32..a54f45b 100644 --- a/docs/website/docs/index.md +++ b/docs/website/docs/index.md @@ -2,6 +2,42 @@ template: home.html --- -# Welcome! +# Towards the future of MongoDB in Kotlin -KtMongo is the future of MongoDB. +In 2016, Julien Buret created [KMongo](https://github.com/Litote/kmongo), a Kotlin driver for MongoDB based on the official Java driver. KMongo added a lot of syntax sugar, making complex queries much more readable and less error-prone thanks to improved type safety: + +```java +// Official Java driver +Bson filter = and(eq("user.gender", "female"), gt("user.age", 29)); +collection. + +find(filter); +``` + +```kotlin +// KMongo +collection.find( + and( + Document::user / User::gender eq "female", + Document::user / User::age gt 29 + ) +) +``` + +In 2023, MongoDB released an official Kotlin driver. Development of KMongo stopped, but the official driver lacked much of the syntax niceties of KMongo, as well as requiring major migration efforts. As a result, many projects decided to keep using KMongo for the foreseeable future. + +We decided to take it upon ourselves to birth the future of MongoDB drivers for Kotlin. KtMongo is based on the official Kotlin driver to ensure we profit from security fixes and new features, and reimplements a DSL inspired by KMongo. + +This project is for **everyone who works with KMongo and is worried about the future after the deprecation notice**, as well as for **everyone dissatisfied with the official Kotlin driver**. + +If you're starting a new project, or are using the official Java or Kotlin drivers, [discover what we can do for you](guides/overview.md). + +## Why not just fork KMongo? + +Since KMongo was started, MongoDB and Kotlin have changed a lot. Aggregation pipelines have become an important tool, type-safety has become more critical. We believe some breaking changes are necessary to bring KMongo into the next decades. + +We intend KtMongo to be the spiritual successor to KMongo, making changes where insight has given us new ideas. We intend to facilitate gradual migration from KMongo to KtMongo such that projects can profit from these new features and official MongoDB support at their own pace. + +To achieve these objectives, KMongo and KtMongo are mutually compatible: they can both be used in a single project. However, KMongo is based on the Java driver, and KtMongo is based on the Kotlin driver, so many classes are slightly different. We recommend adding both libraries together during the migration phase, and migrating files one at a time at your own rhythm, much like when migrating from Java to Kotlin. + +To learn more about the changes we have made, see [the KMongo migration guide](migrate-from-kmongo/why.md). diff --git a/docs/migrate-from-kmongo/nested-fields.md b/docs/website/docs/migrate-from-kmongo/nested-fields.md similarity index 70% rename from docs/migrate-from-kmongo/nested-fields.md rename to docs/website/docs/migrate-from-kmongo/nested-fields.md index 685b520..52ec4b1 100644 --- a/docs/migrate-from-kmongo/nested-fields.md +++ b/docs/website/docs/migrate-from-kmongo/nested-fields.md @@ -1,6 +1,7 @@ -# Migrating from KMongo: referring to nested fields +# Referring to nested documents For the rest of this article, let's take the following example, in which the collection we're interested in is `User`: + ```kotlin class User( val name: String, @@ -19,21 +20,24 @@ class Pet( ) ``` -Referring to a non-nested field is identical with both libraries: +Referring to a non-nested field is identical with KMongo and KtMongo: + ```kotlin User::name eq "foo" ``` -Referring to nested fields is identical with both libraries: +Referring to nested documents is identical with both libraries: + ```kotlin User::country / Country::code eq "FR" ``` Referring to a list item by index uses the `get` operator: -```kotlin -// KMongo + +```kotlin title="Using KMongo" User::pets.pos(4) / Pet::name eq "Chocolat" +``` -// KtMongo +```kotlin title="Using KtMongo" User::pets[4] / Pet::name eq "Chocolat" ``` diff --git a/docs/migrate-from-kmongo/dsl.md b/docs/website/docs/migrate-from-kmongo/search.md similarity index 75% rename from docs/migrate-from-kmongo/dsl.md rename to docs/website/docs/migrate-from-kmongo/search.md index 691ea7a..a4cd0bb 100644 --- a/docs/migrate-from-kmongo/dsl.md +++ b/docs/website/docs/migrate-from-kmongo/search.md @@ -1,12 +1,12 @@ -# Migrating from KMongo: the operator DSL +# Find data KMongo operators primarily work by accepting varargs which are combined into `Bson` documents. KtMongo operators primarily work by exposing a DSL in which operators can be bound. For example, -```kotlin -// KMongo + +```kotlin title="Using KMongo" collection.findOne( and( User::name.exists(), @@ -14,9 +14,10 @@ collection.findOne( ) ) ``` + becomes: -```kotlin -// KtMongo + +```kotlin title="Using KtMongo" collection.findOne { and { User::name.exists() @@ -26,12 +27,14 @@ collection.findOne { ``` Note how: -- the parenthesis become brackets, + +- the parentheses become braces, - the trailing commas are gone. ## Default composition operators Each operation has a default operator when multiple values are passed. For example, `findOne` has a default of `$and`, meaning that these two snippets are identical: + ```kotlin collection.findOne { and { @@ -39,7 +42,9 @@ collection.findOne { User::age gt 18 } } +``` +```kotlin collection.findOne { User::name.exists() User::age gt 18 @@ -48,12 +53,11 @@ collection.findOne { ## Complex requests -One advantage of the DSL syntax is it allows using conditionals directly into the request itself, making complex requests much easier to write. +One advantage of the DSL syntax is using conditionals and loops directly into the request itself, making complex requests much easier to write. -Here's an example with KMongo: +Here's an example of a complex request with KMongo: -```kotlin -// KMongo +```kotlin title="Using KMongo" val bson = ArrayList() if (criteria.name != null) @@ -68,20 +72,20 @@ collection.findOne(and(bson)) When these kinds of requests grow, they become harder to understand because the criteria are defined further from the operation call. With KtMongo, everything is co-located and the intermediate list is eliminated: -```kotlin -// KtMongo + +```kotlin title="Using KtMongo" collection.findOne { if (criteria.name != null) User::name eq criteria.name - + if (criteria.age != null) User::age eq criteria.age } ``` -Since this specific use-case (optional filtering criteria) is so common, KtMongo offers specific operators: -```kotlin -// KtMongo +Since the specific use-case of optional filter criteria is so common, KtMongo offers specific operators that only apply to the request when their argument is non-`null`: + +```kotlin title="Using KtMongo" collection.findOne { User::name eqNotNull criteria.name User::age eqNotNull criteria.age diff --git a/docs/website/docs/migrate-from-kmongo/setup.md b/docs/website/docs/migrate-from-kmongo/setup.md new file mode 100644 index 0000000..9512895 --- /dev/null +++ b/docs/website/docs/migrate-from-kmongo/setup.md @@ -0,0 +1,9 @@ +# Quick start + +## Choose between suspension and blocking + +## Add the dependency + +[//]: # (TODO: publish to central) + +KtMongo is currently not published to MavenCentral. To import it into your projects, use a [Gradle composite build](https://docs.gradle.org/current/userguide/composite_builds.html). diff --git a/docs/website/docs/migrate-from-kmongo/why.md b/docs/website/docs/migrate-from-kmongo/why.md new file mode 100644 index 0000000..25d7d47 --- /dev/null +++ b/docs/website/docs/migrate-from-kmongo/why.md @@ -0,0 +1,122 @@ +# Why migrate from KMongo? + +If you've heard of this project, and are currently using [KMongo](https://litote.org/kmongo/), but aren't quite sure why you should migrate to KtMongo, this page is made for you. + +If you're not using KMongo, but you are using one of the official MongoDB drivers, you may prefer reading [the features overview](../guides/overview.md) instead. + +## KMongo is deprecated + +The first reason, and the instigator for the creation of KtMongo in the first place, is that [KMongo is deprecated](https://litote.org/kmongo/). After the release of the official Kotlin driver in 2023, development of the KMongo project stopped. However, the Kotlin driver doesn't have the type-safe DSL we have grown accustomed to. Because of this, many projects cannot migrate to the official driver: it would require rewriting all queries, for an end result that is less safe and harder to read. + +KtMongo aims to help migrate to the official Kotlin driver: because it reimplements a DSL inspired by KMongo, it is the best of both worlds—your project can continue using a familiar DSL, while internally using the official Kotlin driver. + +## Migration is easy + +KtMongo's DSL does have a few breaking changes as compared to KMongo's, so migration isn't just changing the imports. We make these breaking changes when we believe they improve the safety, performance or readability of the queries. + +The most visible change is the move from `vararg`-based operators to a lambda-based DSL: + +```kotlin title="Using KMongo" +songs.find( + and( + Song::artist / Artist::name eq "Zutomayo", + Song::title eq "Truth in lies", + ) +) +``` + +```kotlin title="Using KtMongo" +songs.find { + and { + Song::artist / Artist::name eq "Zutomayo" + Song::title eq "Truth in lies" + } +} +``` + +As you can see, the main difference is the replacement of parentheses by braces, and the disappearance of the comma at line endings. Other than that, most operators are unchanged, so you'll feel right at home. + +Although you could keep the request as-is, KtMongo actually allows us to simplify this example further, which we'll see [later on this article](#query-dsl-and-optional-filters). + +!!! tip "This migration could be automatic!" +The growth in popularity of OpenRewrite has made it a possible solution to automate these small refactors. We're searching for someone to help us set this up—if you'd like to help, [please contact us](https://github.com/4sh/ktmongo/discussions/21). + +## Migrate at your own pace + +KtMongo and KMongo are compatible, meaning that both can be used in the same project. + +Most projects are structured with one repository class per collection. We recommend migrating one such repository to KtMongo at a time, little by little over the course of multiple releases. This ensures the migration can be done at your own pace, without slowing down the rest of the development. **There is no need to feature-freeze during the migration!** + +[//]: # (TODO: show how to convert a KMongo collection to a KtMongo collection) + +## Query DSL and optional filters + +As we have seen above, the main difference KtMongo makes is to replace `vararg`-based functions by DSLs. This approach brings a few benefits. + +Let's write a query like we would have with KMongo: + +```kotlin +songs.find { + and { + Song::artist / Artist::name eq "Zutomayo" + Song::title eq "Truth in lies" + } +} +``` + +We can simplify this query by removing the `$and` operator: it is implied when a `find` contains multiple filters. + +```kotlin +songs.find { + Song::artist / Artist::name eq "Zutomayo" + Song::title eq "Truth in lies" +} +``` + +Another improvement is the introduction of operators to handle optional filter criteria. For example, with KMongo, if we wanted to make a request and optionally filter by a date, we could write: + +```kotlin title="Using KMongo" +songs.find( + and( + buildList { + add(Song::artist / Artist::name eq artistName) + + if (minDate != null) + add(Song::releaseDate gte minDate) + + if (maxDate != null) + add(Song::releaseDate lte maxDate) + } + ) +) +``` + +The DSL approach by itself eliminates most of the boilerplate of this request, because it allows us to use conditionals directly in the request body: + +```kotlin title="Using KtMongo" +songs.find { + Song::artist / Artist::name eq artistName + + if (minDate != null) + Song::releaseDate gte minDate + + if (maxDate != null) + Song::releaseDate lte maxDate +} +``` + +This is great for building complex queries that have a different structure each time they are called. Loops, conditions, and any other Kotlin language features are available directly in the DSL. + +The specific use-case of optional filters is quite common, so KtMongo provides a purpose-built operator variant: the `notNull` operators apply to the request only if their argument is non-`null`. Using them, we can simplify the request further: + +```kotlin title="Using KtMongo" +songs.find { + Song::artist / Artist::name eq artistName + Song::releaseDate gte minDate + Song::releaseDate lte maxDate +} +``` + +## Avoid using an operator in the wrong context + +## Avoid using expressions that do not match the current collection diff --git a/docs/website/mkdocs.yml b/docs/website/mkdocs.yml index fa4a524..023aa00 100644 --- a/docs/website/mkdocs.yml +++ b/docs/website/mkdocs.yml @@ -1,4 +1,4 @@ -site_name: KtMongo +site_name: KtMongo (experimental) site_author: 4SH & contributors site_description: > Towards the future of MongoDB in Kotlin. @@ -47,14 +47,14 @@ theme: - media: "(prefers-color-scheme: light)" scheme: default primary: green - accent: cyan + accent: teal toggle: icon: material/brightness-7 name: "Light theme (click to switch to dark)" - media: "(prefers-color-scheme: dark)" scheme: slate primary: green - accent: cyan + accent: teal toggle: icon: material/brightness-4 name: "Dark theme (click to switch to system)" @@ -83,6 +83,13 @@ use_directory_urls: false nav: - Home: index.md - - Getting started: [ ] + - Getting started: + - guides/overview.md + - guides/setup.md + - guides/search.md - - Migrating from KMongo: [ ] + - Migrating from KMongo: + - migrate-from-kmongo/why.md + - migrate-from-kmongo/setup.md + - migrate-from-kmongo/search.md + - migrate-from-kmongo/nested-fields.md diff --git a/docs/website/overrides/home.html b/docs/website/overrides/home.html index b161425..b6dd936 100644 --- a/docs/website/overrides/home.html +++ b/docs/website/overrides/home.html @@ -5,14 +5,13 @@ #hero-page { width: 59rem; max-width: 100%; - min-height: 75vh; margin-left: auto; margin-right: auto; } #hero-title { font-size: 3.7rem; - margin-top: 30vh; + margin-top: 10vh; margin-bottom: 1rem; font-weight: bolder; } @@ -36,23 +35,8 @@

{{ config.site_name }}

-

{{ config.site_description }}

-
- - - What is it? - - - - Visit reference - - - - Visit repository -
From 1a4a80aa652eaa60b7503940572d3ebe53c825e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 13 Jul 2024 11:12:06 +0200 Subject: [PATCH 5/5] ci(github): Generate the MkDocs website --- .github/workflows/Website.yaml | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/Website.yaml diff --git a/.github/workflows/Website.yaml b/.github/workflows/Website.yaml new file mode 100644 index 0000000..cbaf50a --- /dev/null +++ b/.github/workflows/Website.yaml @@ -0,0 +1,45 @@ +name: Website + +on: push + +jobs: + mkdocs: + name: Generate the MkDocs website + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Install MkDocs + run: pip install mkdocs-material[imaging] + - name: Generate the website + run: | + pushd docs/website + mkdocs build --site-dir ../../docs-website + popd + ls + - uses: actions/upload-pages-artifact@v3 + with: + path: docs-website + + deploy: + needs: mkdocs + + if: success() && github.ref == 'refs/heads/main' + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4