└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # MongoDB Style Guide
2 |
3 | ## Table of Contents
4 |
5 | 1. [General](#general)
6 | 1. [Enumerations](#enumerations)
7 | 1. [Booleans](#booleans)
8 | 1. [Dates](#dates)
9 | 1. [Null and undefined](#null-and-undefined)
10 | 1. [Other types](#other-types)
11 | 1. [Names](#names)
12 | 1. [Object modelling](#object-modelling)
13 |
14 |
15 | ## General
16 |
17 |
18 | - [1.1](#general--no-surprises) **Avoid surprises**: Your goal when creating a new schema should be to [be as least surprising as you can](https://en.m.wikipedia.org/wiki/Principle_of_least_astonishment). The rules in this style guide are written with this in mind. If there is a trade off between ease of creating data and ease of understanding its structure, choose the latter.
19 |
20 | > Why? Having others be able to understand your data is usually more important than making your code slightly more convenient.
21 |
22 |
23 |
24 | - [1.2](#general--priorities) **Priorities**: There are several dimensions that will impact your database design, such as used hard disk space, read/write speed and ease of development. Spend some time thinking about which ones are important in your case. Then come to the conclusion that ease of development is the only thing that matters because you're not Google.
25 |
26 | > Why? It's easy to fall into the trap of wasting time making things scale from the start. This makes it less likely that you will ever get to a point where these problems become real.
27 |
28 | ## Enumerations
29 |
30 | An enumeration is a type that allows a limited number of values, e.g. a `role` field that can contain the values `'DOCTOR'`, `'NURSE'`, `'PATIENT'` and `'ADMINISTRATOR'`.
31 |
32 |
33 | - [2.1](#enumerations--modelling) **Modelling**: Always model your enums as uppercase string constants, e.g. `'WAITING'`, `'IN_PROGRESS'` and `'COMPLETED'`.
34 |
35 | > Why?
36 | > 1. Favoring strings over booleans or numbers means your data is self-documenting. If your `gender` field contains the value `1`, you don't know if that means male, female or something else.
37 | > 1. Using uppercase strings makes it easy to see at a glance whether something is an enum or a user-facing value.
38 |
39 | | :x: gender | :x: gender | :white_check_mark: gender |
40 | | :---------------- | :---------------- | :----------------- |
41 | | 0 | 'Male' | 'MALE' |
42 | | 1 | 'Female' | 'FEMALE' |
43 |
44 |
45 | - [2.2](#enumerations--explicit) **Explicit states**: Don't use `null`, `undefined`, or any value except upper case string constants in your enums. This includes initial, undecided or unknown states.
46 |
47 | > Why? It makes meaning explicit. If you have an assessmentState field that's set to `null`, you don't know whether that means 'no assessment necessary', 'not applicable', 'patient didn't show up', 'undecided' or any other possible state.
48 |
49 | | :x: assessmentState | :white_check_mark: assessmentState |
50 | | :------------------ | :--------------------------------- |
51 | | null | 'NOT_REQUIRED' |
52 | | *missing* | 'NOT_APPLICABLE' |
53 |
54 |
55 | ## Booleans
56 |
57 |
58 | - [3.1](#booleans--booleans-and-enums) **Booleans and enums**: Don't model things as booleans that have no natural correspondence to true/false, even if they only have two possible states. Use enums instead.
59 |
60 | > Why?
61 | > 1. Booleans can not be extended beyond two possible values, e.g. a boolean field 'hasArrived' could not be changed later to include the possibility of cancelled appointments.
62 |
63 | | :x: gender | :white_check_mark: gender |
64 | | :---------------- | :----------------- |
65 | | false | 'MALE' |
66 | | true | 'FEMALE' |
67 |
68 |
69 | - [3.2](#booleans--prefix) **Prefix**: Prefix your boolean field names with verbs such as 'is...' or 'has...' (e.g. 'isDoctor', 'didAnswerPhoneCall' or 'hasDiabetes').
70 |
71 |
72 | - [3.3](#booleans--orthogonal) **Orthogonality**: If you have several mutually exclusive boolean fields in your collection, merge them into an enum.
73 |
74 | > Why? So that it's impossible to save invalid data in your db, like a car that's green and red simultaneously.
75 |
76 | :x:
77 |
78 | | isRed | isBlue | isGreen |
79 | | :---------------- | :----------------- | :----------------- |
80 | | false | true | false |
81 | | true | false | false |
82 |
83 | :white_check_mark:
84 |
85 | | color |
86 | | :---------------- |
87 | | 'BLUE' |
88 | | 'RED' |
89 |
90 | ## Dates
91 |
92 |
93 | - [4.1](#dates--iso-strings) **ISO strings**: Make sure you never save dates as ISO strings like `'2017-04-14T06:41:21.616Z'`. This can easily happen as a result of JSON deserialization.
94 |
95 | | :x: | :white_check_mark: |
96 | | :---------------- | :----------------- |
97 | | '2017-04-14T06:41:21.616Z' | ISODate('2017-04-14T06:41:21.616Z') |
98 |
99 |
100 |
101 | - [4.2](#dates--day-strings) **Day strings**: Don't use the Date type when all you are concerned with is the day component. Instead, use strings of the form 'YYYY-MM-DD'
102 |
103 | > Why? It makes common operations much easier, like checking whether a date falls on a certain day or getting all the documents that fall on a certain day.
104 |
105 | | :x: dateOfBirth | :white_check_mark: dateOfBirth |
106 | | :---------------- | :----------------- |
107 | | ISODate('1989-10-03T06:41:21.616Z') | '1989-10-03' |
108 |
109 | ## Null and undefined
110 |
111 |
112 | - [5.1](#null-and-undefined--no-overloading) **No overloading**: Don't overload the meaning of `null` and `undefined` to mean anything other than 'unset'.
113 |
114 | > Why? It breaks expectations and makes it impossible to interpret the data by looking at it.
115 |
116 |
117 | - [5.2](#null-and-undefined--sensible-default) **Default**: Don't use null or undefined when there is a sensible default value like `0`, `''` or `[]`.
118 |
119 | > Why? It keeps your types pure
120 |
121 | | :x: notes | :white_check_mark: notes |
122 | | :---------------- | :----------------- |
123 | | 'Some interesting observation' | 'Some interesting observation' |
124 | | null | '' |
125 |
126 | | :x: comments | :white_check_mark: comments |
127 | | :---------------- | :----------------- |
128 | | [ 'First!', 'Great post!' ] | [ 'First!', 'Great post!' ] |
129 | | null | [] |
130 |
131 |
132 | - [5.3](#null-and-undefined--primitive-types) **Primitive types**: In columns that contain primitive types (booleans, numbers or strings) or Date objects, use `null` to express absent values.
133 |
134 | | :x: | :white_check_mark: |
135 | | :---------------- | :----------------- |
136 | | 1 | 1 |
137 | | 2 | 2 |
138 | | *missing* | null |
139 |
140 |
141 | - [5.4](#null-and-undefined--objects-and-arrays) **Complex types**: To express the absence of a value in columns that contain objects or arrays, add another column that determines whether your array or object is present. If it isn't, it should be undefined.
142 |
143 | :x:
144 |
145 | | productType | chapterTitles |
146 | | :---------------- | :----------------- |
147 | | 'BOOK' | ['1 Get Started', '2 The End'] |
148 | | 'CAR' | [] |
149 | | 'CAR' | null |
150 |
151 | :white_check_mark:
152 |
153 | | productType | chapterTitles |
154 | | :---------------- | :----------------- |
155 | | 'BOOK' | ['1 Get Started', '2 The End'] |
156 | | 'CAR' | *missing* |
157 | | 'CAR' | *missing* |
158 |
159 |
160 | - [5.5](#null-and-undefined--dont-mix) **Don't mix the two**: Don't mix `null` and `undefined` in the same column.
161 |
162 | > Why? It makes it hard to understand what the two values are supposed to represent.
163 |
164 | | :x: height | :white_check_mark: height |
165 | | :---------------- | :----------------- |
166 | | 178 | 178 |
167 | | null | null |
168 | | *missing* | null |
169 |
170 |
171 | - [5.6](#null-and-undefined--multiple-missing-value-states) **Multiple missing value states**: If you need two ways to represent the absence of a value, convert the value to an object with `state` and `value` keys.
172 |
173 | :x:
174 |
175 | | weight |
176 | | :------------------------------ |
177 | | null |
178 | | *missing* |
179 | | 97 |
180 | | '' |
181 |
182 | :white_check_mark:
183 |
184 | | weight |
185 | | :------------------------------ |
186 | | { state: 'NOT_APPLICABLE' } |
187 | | { state: 'USER_DOES_NOT_TELL' } |
188 | | { state: 'SET', value: 97 } |
189 |
190 | ## Other types
191 |
192 |
193 | - [6.1](#other-types--mix) **Mixed types**: Don't mix values of different types in one column. Restrict yourself to one type per column.
194 |
195 | > Why? So you don't have to typecheck or cast when you consume the data.
196 |
197 | | :x: |:white_check_mark: |
198 | | :------------ | :---------------- |
199 | | 1 | 1 |
200 | | '2' | 2 |
201 | | { value: 3 } | 3 |
202 |
203 |
204 | - [6.2](#other-types--object-schema) **Object columns**: If you have a column or array that contains objects, make sure all objects share the same schema.
205 |
206 | | :x: | :white_check_mark: |
207 | | :---------------- | :------------- |
208 | | { value: 42 } | { value: 42 } |
209 | | { otherValue: 'foo' } | { value: 43 } |
210 | | { } | { value: null } |
211 |
212 |
213 | - [6.3](#other-types--falsiness) **Falsiness**: Don't use `''` or `0` for their falsiness.
214 |
215 | > Why? It makes it very easy to write buggy code.
216 |
217 | | :x: height | :white_check_mark: height |
218 | | :---------------- | :------------- |
219 | | 182 | 182 |
220 | | 167 | 167 |
221 | | 0 | null |
222 |
223 |
224 | - [6.4](#other-types--numbers-as-strings) **Numbers as strings**: Don't save numbers as strings except when saving data like phone numbers or ID numbers for which leading zeroes are significant and for which arithmetic operations don't make sense.
225 |
226 | > Why? It means you don't have to cast when performing arithmetic or when comparing values.
227 |
228 | | :x: height | :white_check_mark: height |
229 | | :---------------- | :----------------- |
230 | | '178' | 178 |
231 | | '182' | 182 |
232 |
233 | | :x: phoneNumber | :white_check_mark: phoneNumber |
234 | | :---------------- | :----------------- |
235 | | 20123123 | '020123123' |
236 |
237 |
238 | - [6.5](#other-types--numbers-with-unit) **Units**: If you really need to include units with your numbers (think about whether you do, can't you convert and save them in metric?), save them as objects with a `unit` and a `value` field.
239 |
240 | > Why? It makes it possible to perform comparisons and arithmetic operations on your values.
241 |
242 | | :x: | :white_check_mark: |
243 | | :--------------- | :----------------- |
244 | | '10 kg' | { unit: 'KG', value: 10 } |
245 | | '20 stones' | { unit: 'STONES', value: 20 } |
246 |
247 |
248 | - [6.6](#other-types--sets) **Sets**: Model sets as arrays containing uppercase string constants. Use JavaScript's `Set` class in your code where appropriate.
249 |
250 | > Why? You can look at sets as multi-valued enumerations.
251 |
252 | | :x: | :white_check_mark: |
253 | | :---------------- | :----------------- |
254 | | ['Dolphin', 'Pigeon', 'Bee'] | ['DOLPHIN', 'PIGEON', 'BEE'] |
255 | | { DOLPHIN: true, PIGEON: false, BEE: false } | ['DOLPHIN'] |
256 |
257 |
258 | - [6.7](#other-types--object-ids) **ObjectIds**: Don't use MongoDB's ObjectId type. Instead, set _id fields yourself, either by using a property of your data that is naturally unique or by creating random strings.
259 |
260 | > Why? Serializing and deserializing ObjectIds is just too much of a hassle and easily leads to bugs.
261 |
262 | | :x: _id | :white_check_mark: _id |
263 | | :---------------- | :----------------- |
264 | | ObjectId("5937a6a76cb02c00018577fe") | '6xAySKn98aZ66vN' |
265 | | ObjectId("5937a7136cb02c00018577ff") | 'eOiga4lkLaW99ER' |
266 |
267 | ## Names
268 |
269 |
270 | - [7.1](#names--abbreviations) **Abbreviations**: Don't use abbreviations except for domain specific language. When you do abbreviate, capitalize properly.
271 |
272 | > Why? It makes your data and code hard to read.
273 |
274 | - :x: `apTime`
275 | - :white_check_mark: `appointmentTime`
276 | - :x: `ankleBrachialPressureIndexRight`
277 | - :white_check_mark: `ABIRight`
278 | - :x: `healthInformationSystemNumber`
279 | - :x: `hisNumber`
280 | - :white_check_mark: `HISNumber`
281 |
282 |
283 |
284 | - [7.2](#names--key-names) **Case**: Use camelCase over snake_case for key names.
285 |
286 | > Why? It's what we use in JavaScript which means we don't have to convert or mix the two in our code.
287 |
288 |
289 | - [7.3](#names--collection-names) **Collection names**: Collection names should be pluralized and in camelCase. Use dots when there is a relationship between collections (e.g. `users` and `users.appointments`)
290 |
291 | ## Object modelling
292 |
293 |
294 | - [8.1](#object-modelling--growth) **Growth**: Don't let your objects keep growing. Prune and merge properties into nested objects where appropriate (e.g. by combining 'footAssessmentState', 'eyeAssessmentState' and 'nutritionAssessmentState' into a nested 'assessmentStates' object with properties 'foot', 'eye' and 'nutrition')
295 |
296 |
297 | - [8.2](#object-modelling--excessive-nesting) **Nesting**: Don't excessively nest objects. Consider breaking up your data if you find yourself needing deeply nested objects
298 |
299 | ## Todo (pull requests welcome)
300 |
301 | - Normalization/denormalization
302 |
--------------------------------------------------------------------------------