Molecule is a Scala 3 library to query SQL and NoSQL databases with the words of your domain.
Compose an immutable "molecule" data structure:
Person.name.age.Address.street
instead of building queries
SQL | Datalog |
SELECT
Person.name,
Person.age,
Address.street
FROM Person
INNER JOIN Address
ON Person.address = Address.id; |
[:find ?name ?age ?street
:where [?a :Person/name ?name]
[?a :Person/age ?age]
[?a :Ns/address ?b]
[?b :Address/street ?street]] |
and get typed data matching the molecule from the database:
val persons: List[(String, Int, String)] =
Person.name.age.Address.street.query.get
Data can also be fetched asynchronously in a Future
, cats IO
or ZIO
.
- Support for PostgreSQL, SQlite, MySQL, MariaDB, H2 and Datomic databases. More can easily be added
- Molecules for any database behave identically. Each db pass the same SPI compliance test suite (+1700 tests).
- Targets Scala >=3.3.5 on JVM and JS platforms
- Synchronous, Asynchronous (Future), ZIO and cats.effect.IO APIs
- All Scala primitive types and collection types available as molecule attributes (!)
- Typed methods to compose even complex molecules:
- Filter/aggregation functions
- Validation
- Nested and optional relationships
- Sorting
- Pagination (offset/cursor)
- Subscriptions
- Molecules on ScalaJS side transparently operates server database with no JSON marshalling/wiring setup
- Fast transparent binary serialization between Client and Server with Boopickle (no manual setup)
- No macros
- No complex type class implicits
- Maximum type inference in IDE to easily choose available attributes/expressions/relationships
- Define the entity names and attributes of your domain structure with Molecule's meta DSL
object MyDomain extends DomainStructure(5) {
trait Person {
val name = oneString
val age = oneInt
val birthday = oneLocalDate
val address = one[Address]
}
trait Address {
val street = oneString
val zip = oneString
val city = oneString
}
}
- Run
sbt compile -Dmolecule=true
once to generate molecule-enabling boilerplate code and db schemas. - Compose fluent molecules with your domain terms to save and read data from your database.
Molecules using any Database/API combination return the same data, just in different wrappings:
Synchronous API, Datomic
import molecule.datalog.datomic.sync._
val persons: List[(String, Int, String)] =
Person.name.age.Address.street.query.get
Synchronous API, PostgreSQL
import molecule.sql.postgres.sync._
val persons: List[(String, Int, String)] =
Person.name.age.Address.street.query.get
Asynchronous API
import molecule.sql.postgres.async._
val persons: Future[List[(String, Int, String)]] =
Person.name.age.Address.street.query.get
ZIO API
import molecule.sql.postgres.Zio._
val persons: ZIO[Conn, MoleculeError, List[(String, Int, String)]] =
Person.name.age.Address.street.query.get
IO API
import molecule.sql.postgres.io._
val persons: cats.effect.IO[List[(String, Int, String)]] =
Person.name.age.Address.street.query.get
Save one entity
Person
.name("Bob")
.age(42)
.Address
.street("Baker st")
.save.transact
Insert multiple entities
Person.name.age.Address.street.insert(
("Bob", 42, "Baker st"),
("Liz", 38, "Bond road")
).transact
Update
Person(bobId).age(43).update.transact
Delete
Person(bobId).delete.transact
Clone molecule-samples and use one of the template projects to get started.
git clone https://github.com/scalamolecule/molecule-samples.git
Add the following to your build files:
project/build.properties
:
sbt.version = 1.10.11
project/plugins.sbt
:
addSbtPlugin("org.scalamolecule" % "sbt-molecule" % "1.13.0")
build.sbt
:
lazy val yourProject = project.in(file("app"))
.enablePlugins(MoleculePlugin)
.settings(
libraryDependencies ++= Seq(
// One or more of:
"org.scalamolecule" %%% "molecule-sql-postgres" % "0.18.0",
"org.scalamolecule" %%% "molecule-sql-sqlite" % "0.18.0",
"org.scalamolecule" %%% "molecule-sql-mysql" % "0.18.0",
"org.scalamolecule" %%% "molecule-sql-mariadb" % "0.18.0",
"org.scalamolecule" %%% "molecule-sql-h2" % "0.18.0",
"org.scalamolecule" %%% "molecule-datalog-datomic" % "0.18.0",
),
// Paths to directories with your domain structure definition files
moleculeDomainPaths := Seq("app")
)
The coreTests
module in this repo has several domain structure definitions and +1700 tests that show all details of
how molecule can be used. This forms the Service Provider Interface (SPI) that each database implementation needs to comply with
in order to offer all functionality of Molecule and be a valid implementation.
First, clone the molecule project to your computer (or git pull
to get the latest changes):
git clone https://github.com/scalamolecule/molecule.git
cd molecule
Then run some tests on either ScalaJVM or ScalaJS:
Make sure Docker is running to run tests for Postgres, SQlite, Mysql and MariaDB. Datomic and H2 can be run in memory for tests. On a mac you can for instance start Docker Desktop.
Run the coreTests on the jvm with a databases of your choice:
sbt sqlPostgresJVM/test
sbt sqlSQliteJVM/test
sbt sqlMysqlJVM/test
sbt sqlMariadbJVM/test
sbt sqlH2JVM/test
sbt datalogDatomicJVM/test
To test using molecules from ScalaJS, you need to have a ScalaJVM backend server running in a separate process that can receive the queries and send data back to ScalaJS.
In the server
module you can see 7 different minimal Tapir backend setups that you can start out from and successively add your own api endpoints to. In one process you can start up one of those backends where you will be asked which backend you want to use:
sbt server/run
Multiple main classes detected. Select one to run:
[1] molecule.server.Armeria
[2] molecule.server.Http4s
[3] molecule.server.Netty
[4] molecule.server.Pekko
[5] molecule.server.Play
[6] molecule.server.VertX
[7] molecule.server.ZioHttp
Enter number:
Let's enter 3 to use the Netty server:
Enter number: 3
[info] running molecule.server.Netty
Press ENTER to stop the server.
✅ Netty server running on http://localhost:8080
Now we have a backend running on ScalaJVM ready to take care of your molecule queries from ScalaJS!
In another process you can then run one of the following commands to run the coreTests on ScalaJS with the database of your choice:
sbt datalogDatomicJS/test
sbt sqlH2JS/test
sbt sqlMariaDBJS/test
sbt sqlMySQLJS/test
sbt sqlPostgreSQLJS/test
sbt sqlSQliteJS/test
The tests are then automatically fetching data from the running backend - Molecule takes care of it transparently!
Marc Grue
Molecule is licensed under the Apache License 2.0