Repository Rules
- if you see any code that violates these rules, raise it with me directly rather than trying to fix.
- where applicable, file a GitHub Issue.
- adhere to these rules strictly.
General Rules
- if its possible to eliminate an extra try-catch or if-statement at runtime using type-level discipline, do it!
- name your types, functions, and classes appropriately.
- no three-letter acronyms.
- no non-standard contractions.
- each data type has a meaning, pick a name which is accurate and descriptive.
- the average layman should be able to easily understand what your function does using the function signature alone!
- sometimes, there will be exceptions. eg, when you're using specific technical terms that are well understood (saga, event, etc).
- usually, you'll think that your code is an exception to the rules, but it won't be.
State, Functions and Classes
- every function, given the same inputs, should produce the same outputs. ie, no hidden state.
- use classes to prevent fixed state from being mutated arbitrarily (unsafely); methods provide a safe way of interfacing with state.
- if your logic doesn't mutate fixed state, it probably belongs in a standalone function rather than a class.
- functions shouldn't usually produce side-effects (they should be computationally pure).
- if, for example, you're updating a state using an event (computationally pure), and you want to trigger a saga (computational side-effect), store the logic for triggering the saga into an effect handler (a function, capable of producing side-effects, that you pass into an otherwise computationally pure function, so that it may trigger side-effects safely).
Pydantic
- read the Pydantic docs.
- respect the Pydantic docs.
- pydantic is all you need.
- declare and re-use a central
ConfigDict for your use-case, you'll usually want frozen and strict to be True.
Unique ID (UUID) Generation
- inherit from Pydantic's
UUID4 class to create your own UUID class.
- use
uuid.uuid4() to initialize your class with a fresh UUID where possible.
- ensure that idempotency tags are generated by taking the salted hash of persisted state.
- rationale: if a node crashes and resumes from an older state, it should not accidentally re-publish the same event twice under different idempotency tags.
- every distinct function should feature a unique salt, so that there are no accidental collisions in idempotency tags.
Type Wrappers
- reuse types that already exist in the Python standard library.
- when two distinct data types are structurally identical (for example, different IDs which are both UUIDs but shouldn't never mixed up), make sure they can't be conflated by the type system.
- if you're working with a primitive data type (
str, int, etc), use NewType (it has zero runtime overhead).
- if you're working with serializable data objects, consider adding a field (type
str) that states its type.
Type Discipline
- do not bypass the type-checker, preserve strict typing by any means necessary.
- by default, use literal types (like
Literal['one', 'two']) where an enum seems appropriate.
pro-tip: Python's type system is quite complex and feature-rich, so reading the documentation is often advisable; Matt discovered that Python typing library allows you to check that you've implemented a match exhaustively using Literal and get_args(type) after reading the docs.
Use of @final, Freezing
Error Handling
- don't try-catch for no reason.
- make sure that you always know where and when the exceptions your code produces are meant to be handled, so that it's never a nasty surprise.
- always write the rationale for your error-handling down in the docstring!
- communicate the details to your colleagues when appropriate.
Dependencies
- don't introduce any new dependencies without asking.
- don't ask for any dependencies that aren't ubiquitous within production environments.
Commit Messages
- use the imperative mood in the subject line.
- prefix the subject line with a change type. our change types are:
documentation: documentation changes.
feature: a new feature.
refactor: a code change that neither fixes a bug nor adds a feature.
bugfix: a bug fix.
chore: routine tasks, maintenance, or tooling changes.
test: adding or correcting tests.
- restrict the subject line to fifty characters or less.
- capitalize the subject line.
- do not end the subject line with a period.
- separate subject from body with a blank line.