This series of blog posts builds on our previous set on The Challenges of Repeatable and Idempotent Schema Management:
- The Challenges of Repeatable and Idempotent Schema Management: Introduction
- The Challenges of Repeatable and Idempotent Schema Management: Idempotence in Snowflake
- The Challenges of Repeatable and Idempotent Schema Management: Conclusions
This next series of blog posts discuss different approaches to the Management of Database Schemas and the wider topic of Database Object Lifecycle Management, which covers not just data-carrying elements, but all Objects and Relationships in a Database, including tables, views, users, roles, functions, etc. These are handled in Databases as Data Definition Language (DDL) statements (as opposed to Data Manipulation Language (DML), which are the statements for actually adding/removing/updating the data within the Database).
Imperative vs Declarative
In all domains of technology (not just data), there are two fundamental concepts about how you define the change in the state of a system, declarative vs. imperative.
In a declarative approach, you say WHAT you want the state of the system to be but NOT HOW – the engine that implements the declarative definition is responsible for turning the WHAT into the HOW.
Declarative example: 221b Baker Street, Marylebone, London
In an imperative approach, you DON’T define WHAT to achieve, just HOW to achieve it.
Imperative example: face north, walk 1.2 miles, turn right onto Oxford Street, walk 200 yards, turn left, and then it’s 245 ft on the right.
This is full of verbs or actions, “turn” and “walk” – since this approach defines the HOW. One clear difference here is an imperative approach relies on a knowledge of the start point or the current state. The Imperative example above really only works from a single location/state. Following these steps from any other location will not result in the desired end state. In fact, very likely these steps can’t even be followed at all – if my starting location/state is Brisbane, Australia, I can “face north” and “walk 1.2 miles”, but I won’t be able to “turn right onto Oxford Street” – at that point, everything fails, and I’m in an unknown/undefined state.
There are numerous other sources to read about this at different levels of detail. However, one factor that most of these misses is that the imperative and declarative are not two binary options; they are a spectrum. Consider this approach:
Example: Head to London, and when you get there, head to Marylebone station. Head west until you get to Baker Street. Turn right and head north approx. 0.4 miles until you see the London Beetles Store, and 221b Baker street is the next building.
This is a hybrid approach – it’s clearly not a fully declarative approach – there are some explicit steps, but nothing as prescriptive as the imperative approach, but a lot more HOW than the declarative approach. This demonstrates that fully imperative and fully declarative are two points on a continuous spectrum. One interesting observation is that this hybrid approach is much more like human beings working in the real world. It’s a pragmatic mix that will achieve the desired end state from pretty much any start state, but requires a lot less expertise or work on behalf of the traveler.
In the Declarative Example approach, “221b Baker Street, Marylebone, London” is almost impossible to be used directly by anyone – it serves as a great definition of the end state, but I need to use a Declarative Compiler to turn that into something I can use. What could I use in this case? Google Maps™ would work:
Figure 1 - Google Maps as an Example Declarative Compiler
This is just a set of clear, step-by-step instructions – pretty imperative! When we passed a Declarative goal into the Declarative Compiler, we got a set of Imperative steps.
Figure 2 - Declarative Compiler
This is true of virtually all systems, human or machine. At their core, they follow a set of instructions and can only follow a set of instructions. This allows us to look at a Declarative definition not as competing with an Imperative definition, but rather as a higher level of abstraction. Systems take Imperative instructions, we can either provide those directly or define them in a Declarative way and use a compiler to turn this into a set of Imperative instructions, which are then executed by the system.