Skip to content

Entities

In this section we can focus a little bit more on entities and how to define them in your database schema. You've already seen a few examples of entities in the previous sections, so let's dive deeper into the topic. Here's an example definition of user entity:

kotlin
const val domainPackage = "com.example.user"

typealias UserId = String

@Definition(
    domainPackage = domainPackage,
    infrastructurePackage = "$domainPackage.infra",
    apiPackage = "$domainPackage.api",
    versions = [
        DefinitionVersion(
            version = V_1_0_0,
            name = "users",
            properties = [
                Property(name = "id", type = SERIAL, mappedTo = UserId::class, default = "nanoid()"),
                Property(name = "name", type = VARCHAR, details = "16"),
            ],
            constraints = [
                Constraint(type = PRIMARY_KEY, name = "pk_id", on =["id"]),
            ],
            indices = [
                Index(type = UNIQUE_INDEX, name = "uq_name", columns = ["name"])
            ]
        ),
        DefinitionVersion(
            version = V_1_0_1,
            properties = [
                Property(operation = RETYPE, name = "name", type = VARCHAR, details = "24"),
                Property(operation = ADD, name = "display_name", type = VARCHAR, details = "48", nullable = true),
            ],
            indices = [
                Index(operation = REMOVE_INDEX, type = INDEX, name = "idx_id"),
                Index(type = INDEX, name = "idx_id", columns = ["id"])
            ]
        ),
        DefinitionVersion(
            version = V_1_0_2,
            properties = [
                Property(operation = RENAME, name = "display_name", rename = "displayName")
            ]
        )
    ]
)
object UserDefinition

Let's break it down.

Output packages

First of all, you can define output packages for generated classes:

  • domainPackage - package for domain layer of your application
  • infrastructurePackage - package for infrastructure layer of your application
  • apiPackage - package for API layer of your application

This is optional, by default it generates all classes in the same package as where you've defined your @DefinitionVersion.

Serial ID

In most cases, your entities have their own autogenerated ID, that's why we're using SERIAL type for the id property. By default, it'd be represented by auto-incremented integer.

To make this a bit more complicated, let's say we want to use custom nanoid() function to generate our IDs. Unfortunately, Sqiffy can't detect this type automatically, so we have to enforce it by using mappedTo parameter:

kotlin
Property(name = "id", type = SERIAL, mappedTo = UserId::class, default = "nanoid()"),

The UserId is a type alias for String, and that's what Sqiffy will use to represent the id property in the generated API.

Initial version

In the initial version of our schema, we have to define the table name and its properties. If you don't want to use application-side schema versioning (like Sqiffy/Liquibase migrations), you will only need this version.

Note: Keep in mind that that during the initial development process of your application, versioning might not be necessary. To keep it simple, it's usually easier to recreate the database from scratch or update it manually.

Versioning

Once the initial version is done, and you're willing to change your schema, all you need to do is to add a new DefinitionVersion to your schema definition. In further definitions, you're only declaring changes you want to apply to the previous version, not a whole schema.

In the example above, we've added two more versions to our UserDefinition:

  • V_1_0_1 - we've changed the type of name property from VARCHAR(16) to VARCHAR(24), and added a new display_name property
  • V_1_0_2 - we've renamed display_name to displayName

All other properties and constraints remain the same as in the previous version. Other supported operations:

CategoryOperations
Property* ADD - change the type of the property
* RENAME - change the type of the property
* RETYPE - change the type of the property
* REMOVE - change the type of the property
Constraint* ADD_CONSTRAINT - add a new constraint
* REMOVE_CONSTRAINT - remove a constraint
Index* ADD_INDEX - add a new index
* REMOVE_INDEX - remove an index

References

The last thing we want to show you is how to reference other entities in your schema. This is pretty straightforward, you just need to define constriants with FOREIGN_KEY type and reference the other entity's object class:

kotlin
@Definition([
    DefinitionVersion(
        version = V_1_0_0,
        name = "guilds",
        properties = [
            Property(name = "id", type = SERIAL),
            Property(name = "owner", type = INT),
        ],
        constraints = [
            Constraint(
                type = FOREIGN_KEY,
                name = "fk_guild_owner", 
                on = ["owner"], 
                referenced = UserDefinition::class, 
                references = "id"
            )
        ]
    ),
])
object GuildDefinition

That's all!