Database Standards¶
This page contains the agreed upon developer standards as it pertains to databases.
Design Standards¶
Migrations: Goose¶
Goose should be used in conjunction
with SQL files in order to achieve database migrations. Migrations should exist at the top level
of a repository in a migrations/ folder.
Example
migrations/directory:
1 2 3 4 | |
Example
migration.sqlfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Code: GORM¶
GORM should be used in conjunction with Go structs in order to tie database models to their underlying records:
Example struct with GORM:
1 2 3 4 5 6 | |
Code: Validation and Mutation¶
Use Golang as opposed to writing complex functions in SQL, we should use proper code to validate inputs before inserting into the database. Additionally, we should also use proper code to mutate any incoming database records.
Example (Do not do this):
Here is a sample SQL file with complex trigger functions (logic in SQL):
1 2 3 4 5 6 7 8 9 10 11 12 | |
Example (Do this instead):
Here is the same logic, written in code:
1 2 3 4 5 6 7 8 9 | |
The above shows an example of a trigger function in SQL for validation. While this does enforce the data inserted closest to where it lives, it creates unneeded complexity and additional technical debt (need to understand writing of SQL functions and the codebase that calls it) that we want to avoid. Additionally, services that run proper code are much easier to scale horizontally, so it is better to use resources outside of the database in proper code than to stuff it all in the database.
However, there is are exceptions to the rule...
Code: Validation Exceptions¶
-
Length Validation: In the instance of length validation, it is better to enforce length validation at the database level to avoid the need to validate in code and to enforce the data integrity of what is stored in the database. This is specific to string data which can be of variable length. We should look to implement string length validation in one of two manners:
-
Variable Length: for variable length strings (strings which do not have a predetermined length), we should use the
VARCHAR(n)column type:
1 | |
In the above example, a version_id can be different lengths (e.g. v0.1.0 versue v0.1.10), so we
should use the VARCHAR(n) type. This allows the database to not preallocate space and validates
that the data only allows a specific length (32).
- Non-Variable Length: for string data that has a pre-determined length, we should use the
CHAR(n)column type:
1 2 | |
In the above example, a sha256_sum and commit_hash have pre-determined lengths and will always
only be those lengths, so we use the CHAR(n) to preallocate the space.