-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
chore(slo): Introduce domain models for SLO #142154
Conversation
This is an attempt to introduce a proper domain models so most of the business logic can be contained within the SLO domain models instead of being spread into different services. There is some drawback with the need to type the stored SLO that I need to investigate further
c044ebb
to
71c1de1
Compare
💔 Build FailedFailed CI StepsTest Failures
Metrics [docs]Unknown metric groupsESLint disabled in files
ESLint disabled line counts
Total ESLint disabled count
HistoryTo update your PR or re-run it, just comment with: |
return getSLOTransformId(slo.id); | ||
} | ||
|
||
private buildSource(slo: APMTransactionErrorRateSLO) { | ||
private buildSource(slo: SLO, indicator: APMTransactionErrorRateIndicator) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 I have to provide the slo and the indicator separately otherwise the slo.indicator
type cannot be inferred correctly. Except if I safeguard the slo.indicator.type in every private functions as done in the main function: https://github.com/elastic/kibana/pull/142154/files#diff-21eb1f91184393609c9483ec0f5c84d8cd0ee5ffb934065f338539bb79e2bad8R27.
|
||
const createSLOParamsSchema = t.type({ | ||
body: commonSLOSchema, | ||
body: t.type({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 rest_specs
now contains only the API schema as well as the application service params (input) and response (output).
if (objective.target > 1 || objective.target <= 0) { | ||
throw new InvalidParameter('Invalid objective target'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 Business logic is enforced at creation, thus impossible state becomes impossible. Ideally, each value object should enforce their business rules, but in this case, Objective is a simple type derived from the io-ts schema.
@@ -29,7 +29,7 @@ describe('DeleteSLO', () => { | |||
|
|||
describe('happy path', () => { | |||
it('removes the transform, the roll up data and the SLO from the repository', async () => { | |||
const slo = createSLO(createAPMTransactionErrorRateIndicator()); | |||
const slo = createSLO(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💬 The createSLO fixture function uses an APMTransactionErrorRateIndicator by default.
Closing for the timebeing, found a more elegant solution |
📝 Summary
This is an attempt to introduce a domain models so most of the business logic can be contained within the domain models instead of being spread into different services.
Types are necessary, and runtime validation with io-ts is great at the boundaries (rest api), but it cannot validate that the whole object is valid from a business perspective. For example, enforcing that the target objective is a number between ]0;1].
Domain models ensure the business rules are valid, and provide behaviours that abstract the business logic away from the consumer, e.g. we can imagine a method
slo.replaceIndicator(newIndicator)
which would replace the indicator but also bump the internal revision.The
Indicator
domain models are derived directly from the io-ts schema. It's not ideal since the io-ts schemas can have some optional fields (in order to make the REST api lighter) than the domain models should enforce with some sane default value while being constructed.But creating a proper domain model was also introducing a lot of boilerplate that the io-ts schema abstracted, thus, I decided to skip this part for the moment.
Now, I don't want to over-engineer this, and I'd like your thoughts on this.
Is it overkill? Do you have a simpler approach in mind? Should we just have two io-ts schemas, one for the rest api, and one for the domain models, with some commons and some domain function to provide behaviours?