navapbc / template-application-flask

Apache License 2.0
9 stars 4 forks source link

[DO NOT MERGE - proof of concept] Lookup values based on enums with DB validation #156

Closed chouinar closed 1 year ago

chouinar commented 1 year ago

This is only a proof of concept - using the template repo for easy of sharing across teams, no intention to merge this.

Changes

Modified how lookup values work making them derived directly from enums, but stored in the DB as text-foreign keys to handle validation at the DB layer.

Note that the implementation is very hacky, and poorly organized.

Context for reviewers

I'll start by saying that this idea might be more than necessary, but for DBs that we really want ease-of-use in-code, as well as validation of lookup values in the DB, it would cover that with minimal complexity.


This is an idea that joins together various different solutions to get something that I hope is somewhat intuitive. First, a bit of context:

Many systems require the use of lookup values, IDs that represent a limited set of potential values. For example, the US states (CA, NJ, MI, etc.), different claim statuses (open, closed, pending), race, ethnicity, address type, and many many more.

The biggest challenge of managing these values is:

While we have tried various solutions for this in the past, the tools we use with SQLAlchemy, alembic and Postgres make it difficult to do so. While Postgres has an enum type and/or constraints you can use, you cannot change an existing enum/constraint. The only way to do so is to drop the constraint and remake it. Enums can be added to, but removal also requires dropping and remaking it, which in a live system could cause problems.

In a prior project, we solved this with lookup tables, which were essentially data tables we populated when the application started up (or more generally, just synced differences to). For any lookup values, we would specify foreign keys to these lookup value records. While this works, the usage in-code was clunky, and the amount of code necessary to make it work was a lot as several caches had to be maintained to avoid constantly fetching the lookup value records from the DB.


So, what did I do here? I tried to combine the best components of each solution. In-code, only Python enums are used. The LookupEnum type decorator created handles converting the enum to/from a string which makes using it as a developer uneventful. When creating or updating columns, you just keep working with enums, and the DB itself is irrelevant.

As for the DB itself, we still maintain that foreign-key lookup validation approach. We never need to reference the records from that table, and the foreign keys basically serve as a mutable constraint.


Additional ideas, beyond just cleaning this up:

Testing

Adding a new enum value and restarting the app makes it an allowed value in both the API, as well possible to store in the DB.

Screen Shot 2023-03-17 at 10 43 07 AM

Screen Shot 2023-03-17 at 10 43 15 AM