-
Notifications
You must be signed in to change notification settings - Fork 0
#437 adr on overlap validation #454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
80777d7
docs: #437: add ADR on overlaps validation
katariniss e0723b2
docs: #437: add numeration to ADRs
katariniss b2181ff
docs: #437: remove redundant word in ADR
katariniss e90c56f
doc: #437: change color text in table from 002-time-api-overlap-valid…
SaraevaAnastasia c8ea4ff
docs: #437: add another alternative and more context to ADR on overla…
katariniss 5959df0
Merge branch 'feature/#437-ADR-on-overlap-validation' of github.com:T…
katariniss c7b7f46
docs: #437: move ADR on overlap validation to time-tracker folder
katariniss 99f6faa
docs: #437: add comments to code blocks in ADR on overlap validation
katariniss 3922474
Update time-tracker/adrs/003-time-api-overlap-validation.md
MDI74 859add8
Update time-tracker/adrs/003-time-api-overlap-validation.md
MDI74 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # Tenants ADR | ||
| # 001: Tenants | ||
| ## What is Tenant? | ||
| Organization/Client to split data between them. | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # 003: Strategy of Validation for Time Intervals Overlapping and Table Structure | ||
|
|
||
| ## Status | ||
|
|
||
| Accepted (2026-02-13) | ||
|
|
||
| ## Context | ||
|
|
||
| We need to validate the overlaps of time intervals between different types of entries (task, overtime, make-up time, lunch, away, sick leave, day off, unwell, etc.) in the time tracker. | ||
|
|
||
| ### Requirements: | ||
|
|
||
| * Validation must ensure data integrity and consistency. | ||
| * Clear and precise errors must be returned to the client. | ||
|
|
||
| ### Intersection Rules: | ||
|
|
||
| | | Task | Overtime | Make-up time | Lunch | Unwell | Day-off | Late | Sick leave | Vacation | Away | | ||
| | :---- | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | | ||
| | **Task** | ${\color{blue}-}$ | **\+** | **\+** | **\-** | **\-** | **\+** | **\-** | **\+** | **\+** | **\-** | | ||
| | **Overtime** | **\+** | ${\color{blue}-}$ | **\-** | **\-** | **\-** | **\+** | **\-** | **\+** | **\+** | **\-** | | ||
| | **Make-up time** | **\+** | **\-** | ${\color{blue}-}$| **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | | ||
| | **Lunch** | **\-** | **\-** | **\-** | ${\color{blue}-}$ | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | | ||
| | **Unwell** | **\-** | **\-** | **\-** | **\-** | ${\color{blue}-}$| **\-** | **\-** | **\-** | **\-** | **\-** | | ||
| | **Day-off** | **\+** | **\+** | **\-** | **\-** | **\-** | ${\color{blue}-}$| **\-** | **\-** | **\-** | **\-** | | ||
| | **Late** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | ${\color{blue}-}$ | **\-** | **\-** | **\-** | | ||
| | **Sick leave** | **\+** | **\+** | **\-** | **\-** | **\-** | **\-** | **\-** | ${\color{blue}-}$ | **\-** | **\-** | | ||
| | **Vacation** | **\+** | **\+** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | ${\color{blue}-}$ | **\-** | | ||
| | **Away** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | **\-** | ${\color{blue}-}$ | | ||
|
|
||
| ## Decision | ||
|
|
||
| Validate time overlaps only at the database level using constraints. This solves race conditions and data integrity issues. This solution has a technical limitation: range intersection constraints only work within a single table - we cannot add a constraint that will validate overlaps acreoss multiple tables. Initially, we had separate tables for work entries , i.e. tasks, and for adjustments (e.g., overtime, make-up time), so it wouldn't be possible to use range intersection constraints. So we had to unite all entries to a single table using the [TPH (table-per-hierarchy)](https://learn.microsoft.com/en-us/ef/core/modeling/inheritance#table-per-hierarchy-and-discriminator-configuration) inheritance type, which has one base table from which all others inherit. | ||
|
|
||
| These constraints create overlap rules that only apply to specific subsets of rows, not all rows in the table. | ||
|
|
||
| *Example: Task cannot intersect with Unwell.* | ||
|
|
||
| | | Task | Unwell | | ||
| | :---- | :-----: | :-----: | | ||
| | **Task** | \- | \- | | ||
| | **Unwell** | \- | \- | | ||
|
|
||
| ``` | ||
| // Constraint 1: Only applies to rows where type is 1 OR 2 | ||
| ALTER TABLE tracked\_entries | ||
| ADD CONSTRAINT exclude\_type12\_overlap | ||
| EXCLUDE USING GIST ( | ||
| tsrange(start\_time, end\_time, '\[)') WITH && | ||
| ) | ||
| // Filters which rows the constraint applies to | ||
| WHERE (type IN (1, 2)); | ||
| ``` | ||
|
|
||
| *Example: Task can overlap with Overtime, but Unwell cannot overlap with any other entry.* | ||
|
|
||
| | | Task | Unwell | Overtime | | ||
| | :---- | :-----: | :-----: | :-----: | | ||
| | **Task** | \- | \- | \+ | | ||
| | **Unwell** | \- | \- | \- | | ||
| | **Overtime** | \+ | \- | \- | | ||
|
|
||
| ``` | ||
| // Constraint 1: Only applies to rows where type is 1 OR 2 | ||
| ALTER TABLE tracked\_entries | ||
| ADD CONSTRAINT exclude\_type12\_overlap | ||
| EXCLUDE USING GIST ( | ||
| tsrange(start\_time, end\_time, '\[)') WITH && | ||
| ) | ||
| // Filters which rows the constraint applies to | ||
| WHERE (type IN (1, 2)); | ||
|
|
||
| // Constraint 2: Only applies to rows where type is 2 OR 3 | ||
| ALTER TABLE tracked\_entries | ||
| ADD CONSTRAINT exclude\_type23\_overlap | ||
| EXCLUDE USING GIST ( | ||
| tsrange(start\_time, end\_time, '\[)') WITH && | ||
| ) | ||
| // Filters which rows the constraint applies to | ||
| WHERE (type IN (2, 3)); | ||
|
|
||
| ### Generating User-Friendly Errors | ||
|
|
||
| To generate user-friendly errors, an additional query is made to the database at the code level to obtain information about the conflicting record and pass its type to the error text. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
|
|
||
| * Data integrity is guaranteed at the database level which protects from concurrent writes. | ||
| * No need to create too many constraints. | ||
| * Errors for the client remain flexible and meaningful because we additionally check them in the code. | ||
| * Cross-table overlap checks (which is impossible with constraints) are not required. | ||
|
|
||
| ### Negative | ||
|
|
||
| * All validated records must be stored in a single table. | ||
| * The logic for grouping types in constraints requires careful maintenance. | ||
| * An additional query is added to handle constraint errors. | ||
| * It is tricky to test all variations of range overlaps, and the complexity will grow when new types will be added. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| ### Only database constraints without additional processing | ||
|
|
||
| Relying solely on database errors without additional analysis. | ||
|
|
||
| #### Cons: | ||
|
|
||
| * Unable to generate clear error messages for the client. | ||
|
|
||
| ### Validation at the code level | ||
|
|
||
| All overlap checks are performed only in the backend code. | ||
|
|
||
| #### Cons: | ||
|
|
||
| No protection against concurrent writes. | ||
|
|
||
| ### Separate record types across multiple tables | ||
|
|
||
| Storing different record types in different tables. | ||
|
|
||
| #### Cons: | ||
|
|
||
| * Unable to validate overlaps between tables using constraints. | ||
| * Data model and queries become more complex. | ||
|
|
||
| ### Optimistic Locking with Daily Aggregation | ||
|
|
||
| Instead of complex database constraints and TPH inheritance, we can fundamentally rethink the storage schema so that a single row stores data for an employee's entire day. Then we can remove the complex jumble of constraints in the database. | ||
|
|
||
| #### Cons: | ||
|
|
||
| * Cross-day overlap validation will require complex validation logic. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.