Contracting¶
Table of Contents¶
- Contracting
- Table of Contents
- Quick links
- Overview
- Taxonomy
- Sub components
- Technical architecture \& patterns
- đŠâđŠâđĻ Sub-components
- File Structure Example
- đ¨ Typing
- đ Logging
- đĨ Error Management
- đŠī¸ Commit \& Side Effects
- đ Performance
- đ Public \& protected API
- đ State Machine
- đ§âđĢ Views \& Controllers
- đ Transfer Data from Turing
- đ Async Processing
- âī¸ UI \& Frontend
- đ Server-Side Rendering
- Resources
This component is responsible for everything related to contract, subscription, and legal document management. The component mostly follows the Modular Monolith architecture, with a few exceptions due to being in a migration phase.
This component is not responsible for managing offers or accounts.
The component is designed to be global and used across all countries.
- The
publiclayer provides the component's public API. It's the only layer that should be used by other components to interact with contracting. - The
externallayer provides an abstraction layer that enables usage across different countries (in other words,contractinginteracts with the outside world through this layer)
Quick links¶
- Datadog service monitoring â§
- Sentry â§
- Slack channel: #crew_contract_lifecycle
- Linear team â§
- Account hub â§ (as an entrypoint)
Overview¶

Taxonomy¶
[!WARNING] This component aims to progressively consolidate all contract management functionality. As this is an ongoing migration, there are some taxonomy inconsistencies worth noting.
- Offer (Version): also called Product or Plan in some contexts. It's something that is sold to a customer. It includes a service definition (like a coverage), a price and usually a configuration.
- Subscription (Period): also called Contract (Version) in some contexts. It's the instantiation of an offer for a customer. It includes a start date, an end date, a reference to the plan, to the customer, a configuration, and attach legal documents.
- Legacy Contract: also mentioned as cancellation, we usually refer to the need to cancel the previous customer' contract on his behalf
You can find more details about the taxonomy on the dedicated Notion page â§.
Sub components¶
As this component's scope is large, we are splitting it into smaller components to improve maintainability and decouple responsibilities. It follows the Modular Monolith architecture (subcomponents pattern).
The most important sub-components are:
dashboard: responsible for providing endpoints and logic to display the customer dashboard (about managing contracts)account_hub: responsible for providing endpoints and logic to display the account hub (about managing contracts & renewal)proposal: responsible for the creation and management of proposals that are the entry point to create / amend contracts.renewal: responsible for the renewal of contracts, it manages & orchestrates the process and the toolssubscription: responsible for the lifecycle of subscriptions (only used for france prev amendment & belgium health)insight: responsible for providing general metrics (like demographics and financial performance) at account, company and contract level). It does not aim to provide those metrics outside the component, but it's rather for internal use.cancellation: responsible for managing the cancellation of previous legacy contracts
Technical architecture & patterns¶
Here â§ the documentation about the modular monolith.
đŠâđŠâđĻ Sub-components¶
[!NOTE] These are guidelines rather than strict rules. Sometimes there are valid reasons to deviate, and we don't want to overengineer the codebase. Please contact the maintainers if you have any questions.
Each subcomponent:
- Defines its public interface through
public - Hides implementation details in
internal - Defines its own models in
internal/models - Can interact with other subcomponents through their
protectedinterface - Can join models from other subcomponents (especially for performance reasons) â but should minimize this when possible
- Can define HTTP endpoints if they are within the component's scope. For example:
- The internal module
dashboarddefines endpoints to display the customer contract dashboard - This aligns with the component's scope and doesn't violate modular monolith principles
- Can define public methods for use by other components. These should be exposed through
contracting/publicand not imported directly from the submodule - Should remain as decoupled as possible from other subcomponents
- For instance,
proposalshould not directly importrenewal - Adding dependencies increases the submodule's responsibilities â which we aim to keep minimal
- We recommend against using event systems for inter-subcomponent communication. Direct method calls are preferred
- If you need to use events, especially to avoid adding dependencies:
- Consider using the
EventHandlerpattern - Consider direct localized method calls
- Consider using the
File Structure Example¶
contracting
âââ public
âââ subcomponents
â âââ account_hub
â â âââ README.md # optional but recommended
â â âââ internal
â â â âââ models
â â â â âââ my_model.py
â â â âââ controllers
â â â â âââ list_subscriptions.py
â â â â âââ get_subscription.py
â â â âââ presenters
â â â âââ utils
â â âââ protected
â âââ ...
âââ external
â â âââ admin
â â â âââ fr
â â â âââ es
â â â âââ be
â â â âââ api
đ¨ Typing¶
- Everything must be typed
đ Logging¶
- We aim to log all significant business events
- All non-read-only public methods should include logging
- We maintain consistent log attributes across the component, especially IDs to identify entities
- For example, all logs triggered in
proposalshould includeproposal_idwhen relevant - This makes it easier to trace business events related to a specific proposal
- The same applies for subscriptions and other entities
- Events should be logged even when not committed. We typically add a
commitlog parameter to indicate when an event might not be committed yet.
đĨ Error Management¶
- Business errors should be defined
ContractingMessagewhich are eitherContractingErrororContractingWarning - Business errors define
codeto identify them â we should enforce itmessageto display to the user, we accept markdown to make it easier to formatcontextto provide additional information to the userrequire_acknowledgement_linkto indicate if the error should be acknowledged by a third user- They are persisted in the database by default (in
proposalmodule only) - They can be raised by any method or accumulated (in order to list all errors at the end of a request, instead of failing fast) - then raised all at once in
ContractingValidationError. Seeassert_no_validation_error - An object
ValidationContextcan be provided to tune the behaviour of the error management. For instance, it's possible to bypass some warnings or errors. Seeassert_no_validation_error
đŠī¸ Commit & Side Effects¶
- All public methods must include a
commitparameter that defaults toTrue - When
commitisTrue, the method should commit the database session and trigger any potential side effects - When
commitisFalse, the method should neither commit the database session nor trigger any side effects - For low-level internal methods that need to trigger side effects like enqueuing jobs or sending emails, use the
shared/side_effectmodule to defer these actions until the request is successfully committed.
đ Performance¶
- This component is configured as a standalone service in Datadog
- All public methods must be traced with the
@contracting_tracer_wrapdecorator
đ Public & protected API¶
- All public & protected methods must include documentation with docstrings
- We are cautious when exposing new methods as they represent a long-term maintenance commitment
- We strive to keep the public API minimal
- We design our APIs to be composable and flexible
- We aim for a single method to do one thing
- We try to leverage parameters to avoid having too many methods & increase flexibility & expressiveness
- DONT:
list_subscriptions_for_account(account_id: UUID) - DO:
list_subscriptions(account_id: UUID | None = None, subscription_id: UUID | None = None, ...) - DONT:
delete_account_if_it_has_no_entity(account_id: UUID) - DO:
delete_account(account_id: UUID, only_if_no_entity: bool = True)
đ State Machine¶
- When relevant, we try to use a state machine to represent the different states & transitions of a business entity
- We rely on
contracting/utils/state_machine
đ§âđĢ Views & Controllers¶
- Keep views and controllers simple and free of business logic
- Organize files by responsibility and action
- File names should be descriptive and start with a verb (e.g.,
create_proposal.py). See examples incontracting.internal.proposal.views.* - We recommend creating a presenter layer to prepare data for views. This is ideal for non-business logic needed to prepare view data.
đ Transfer Data from Turing¶
Turing is our data warehouse for computing and aggregating data. We use a dedicated service to transfer data from Turing to our database. See documentation â§.
- All models should have the
Turingprefix to clearly indicate their origin - These tables should be read-only and not modified by the component itself
đ Async Processing¶
- We recommend using
async_compute_resultfor long background tasks that need to return results to users
âī¸ UI & Frontend¶
- We use Mantine & React for the UI
- We maintain simple UI design and avoid embedding logic in the UI layer
- All data gathering logic should reside in
account_hub/internalordashboard/internal
đ Server-Side Rendering¶
[!NOTE] We have discontinued server-side rendering in favor of Mantine & React. This section is retained for historical context.
We're using plain old HTML to build the internal tools around this component. We believe this is an interesting option to explore compared to using a full blow React application. It allows us to iterate faster, with less boilerplate and processes.
- We try to avoid using JS / jquery as much as possible. We have a single 180 lines of JS file
- We try to avoid to have too much logic in the templates. We try to keep them as simple as possible, leveraging the presenter pattern (to prepare data)
Resources¶
- Article about it there â§
- More discussions about it in that issue â§.