-
Notifications
You must be signed in to change notification settings - Fork 17
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
BC-7772 introduce ddd objects #5167
base: main
Are you sure you want to change the base?
Changes from all commits
10964eb
9739d3a
8d1435a
ac026eb
1e30fdb
12eaf4c
9916fa5
6ed8437
6e095be
0dd317b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export { TypeGuard } from './type.guard'; | ||
export { TypeGuard, PrimitiveType, PrimitiveTypeArray, ObjectType } from './type.guard'; | ||
|
||
// Guards at different places exists insdide the modules, as validation as utils. | ||
// Please consolidate it and make it explicit as guard. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import { TypeGuard } from '@shared/common'; | ||
import { ValueObject } from './value-object'; | ||
|
||
describe('ValueObject', () => { | ||
describe('By passing valid values', () => { | ||
const setup = () => { | ||
class Name extends ValueObject<string> {} | ||
|
||
const nameThor1 = new Name('Thor'); | ||
const nameThor2 = new Name('Thor'); | ||
|
||
return { nameThor1, nameThor2 }; | ||
}; | ||
|
||
it('should be return true by same reference', () => { | ||
const { nameThor1 } = setup(); | ||
|
||
expect(nameThor1.equals(nameThor1)).toBe(true); | ||
}); | ||
|
||
it('should be return true by different references', () => { | ||
const { nameThor1, nameThor2 } = setup(); | ||
|
||
expect(nameThor1.equals(nameThor2)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('By usage of primitive array as value object', () => { | ||
const setup = () => { | ||
class Names extends ValueObject<string[]> {} | ||
|
||
const nameThors1 = new Names(['Thor1', 'Thor2', 'Thor3']); | ||
const nameThors2 = new Names(['Thor1', 'Thor2', 'Thor3']); | ||
const nameThorsDifferent = new Names(['Thor1', 'Thor2', 'Thor4']); | ||
const nameThorsDifferentCount = new Names(['Thor1', 'Thor2']); | ||
|
||
return { nameThors1, nameThors2, nameThorsDifferent, nameThorsDifferentCount }; | ||
}; | ||
|
||
it('should be return true by same reference', () => { | ||
const { nameThors1 } = setup(); | ||
|
||
expect(nameThors1.equals(nameThors1)).toBe(true); | ||
}); | ||
|
||
it('should be return true by different references', () => { | ||
const { nameThors1, nameThors2 } = setup(); | ||
|
||
expect(nameThors1.equals(nameThors2)).toBe(true); | ||
}); | ||
|
||
it('should be return false by different values inside the array', () => { | ||
const { nameThors1, nameThorsDifferentCount } = setup(); | ||
|
||
expect(nameThors1.equals(nameThorsDifferentCount)).toBe(false); | ||
}); | ||
|
||
it('should be return false by different values inside the array', () => { | ||
const { nameThors1, nameThorsDifferent } = setup(); | ||
|
||
expect(nameThors1.equals(nameThorsDifferent)).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('When value object with validation is created.', () => { | ||
const setup = () => { | ||
class Name extends ValueObject<string> { | ||
protected validation(value: unknown): boolean { | ||
let isValid = false; | ||
|
||
if (TypeGuard.isString(value) && this.isValidLength(value)) { | ||
isValid = true; | ||
} | ||
|
||
return isValid; | ||
} | ||
|
||
private isValidLength(value: string): boolean { | ||
let isValid = false; | ||
|
||
if (value.length > 0 && value.length <= 5) { | ||
isValid = true; | ||
} | ||
|
||
return isValid; | ||
} | ||
} | ||
|
||
return { Name }; | ||
}; | ||
|
||
it('validation should be execute and throw if not valid', () => { | ||
const { Name } = setup(); | ||
|
||
const expectedError = new Error('ValueObject Name validation is failed for input ""'); | ||
expect(() => new Name('')).toThrowError(expectedError); | ||
}); | ||
|
||
it('should execute but not throw if is valid value is passsed', () => { | ||
const { Name } = setup(); | ||
|
||
expect(new Name('Thor')).toBeDefined(); | ||
}); | ||
/* | ||
it('should execute but not throw if is valid value is passsed', () => { | ||
const { Name } = setup(); | ||
|
||
const myName = new Name('Thor'); | ||
|
||
expect(myName.isValidValue('123456')).toBe(false); | ||
expect(myName.isValidValue('')).toBe(false); | ||
expect(myName.isValidValue('ABC')).toBe(true); | ||
}); | ||
*/ | ||
}); | ||
|
||
describe('When value object with modification is created.', () => { | ||
const setup = () => { | ||
class Name extends ValueObject<string> { | ||
protected modified(value: string): string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replacing the boilerplate with array of functions that can be execute will improve the usability In this case it is also easy to use same hooks for different value objects. But it is harder to implement with ts. We should look togehter into it. |
||
let modifedValue = value; | ||
|
||
modifedValue = this.removeWithspacesAtBeginning(value); | ||
modifedValue = this.truncatedToLength4(modifedValue); | ||
|
||
return modifedValue; | ||
} | ||
|
||
private removeWithspacesAtBeginning(value: string): string { | ||
return value.trimStart(); | ||
} | ||
|
||
private truncatedToLength4(value: string): string { | ||
return value.substring(0, 4); | ||
} | ||
} | ||
|
||
return { Name }; | ||
}; | ||
|
||
it('should be execute all modifications by creating a new instance', () => { | ||
const { Name } = setup(); | ||
|
||
const myName = new Name(' Thor the God of Thunder!'); | ||
|
||
expect(myName.value).toEqual('Thor'); | ||
}); | ||
}); | ||
|
||
describe('By passing invalid values', () => { | ||
const setup = () => { | ||
class Name extends ValueObject<string> {} | ||
class OtherString extends ValueObject<string> {} | ||
// class InvalidType extends ValueObject<{}> {} --> object and functions do not work | ||
// TODO: Test for array | ||
|
||
const nameThor1 = new Name('Thor'); | ||
const nameUnhappy = new Name('Unhappy'); | ||
const otherThor = new OtherString('Thor'); | ||
const fakeThor = { | ||
value: 'Thor', | ||
equals: (vo: ValueObject<string>): boolean => vo === this, | ||
}; | ||
|
||
return { nameThor1, nameUnhappy, otherThor, fakeThor }; | ||
}; | ||
|
||
it('should be return false by different value', () => { | ||
const { nameThor1, nameUnhappy } = setup(); | ||
|
||
expect(nameThor1.equals(nameUnhappy)).toBe(false); | ||
}); | ||
|
||
it('should be return false if value object is passed ', () => { | ||
const { nameThor1, fakeThor } = setup(); | ||
|
||
// @ts-expect-error Test case | ||
expect(nameThor1.equals(fakeThor)).toBe(false); | ||
}); | ||
|
||
it('should be return false by different ValueObjects and same value', () => { | ||
const { nameThor1, otherThor } = setup(); | ||
|
||
expect(nameThor1.equals(otherThor)).toBe(false); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { TypeGuard, PrimitiveType, PrimitiveTypeArray } from '@shared/common'; | ||
|
||
type ValueObjectTyp = PrimitiveType | PrimitiveTypeArray; | ||
|
||
export abstract class ValueObject<T extends ValueObjectTyp> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ergibt es überhaupt Sinn eine abstrakte Klasse für Value Objects einzuführen? Geht uns dadurch nicht ganz viel Fachlichkeit verloren, wenn zB der Wert immer Value heißt? Was an der abstrakten Klasse bietet wirklich einen Mehrwert beim entwickeln? |
||
public readonly value: T; | ||
|
||
constructor(value: T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: |
||
// TODO: No Test for the execution order exists for now, but we must clarify if we want first the modifcation, or first the validation | ||
// For operations with truncat before make more sense. Adding before/after modifications are also possible, but it can be overload the interface | ||
const modifiedValue = this.modified(value); | ||
this.checkValue(modifiedValue); | ||
this.value = Object.freeze(modifiedValue); | ||
} | ||
|
||
/** Use this method with override for add validations. */ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
protected validation(value: unknown): boolean { | ||
return true; | ||
} | ||
|
||
/** Use this method with override for add modifications, before execute the validation. */ | ||
protected modified(value: T): T { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: |
||
// TODO: Why eslint think that T is from type any is unlear for me. | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
return value; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: |
||
// TODO: make this method sense if we not can add this as static MyValueObject.isValidValue() ? | ||
// Should it be optional by adding in extended class, or as abstract that it must be implemented? | ||
/* | ||
public isValidValue(value: unknown): boolean { | ||
return this.validation(value); | ||
} | ||
*/ | ||
// TODO: Same questions make it sense without static to set it public? | ||
// If not than we can change it to private for a less overloaded interface at the value object | ||
private checkValue(value: unknown): void { | ||
if (!this.validation(value)) { | ||
throw new Error(`ValueObject ${this.constructor.name} validation is failed for input ${JSON.stringify(value)}`); | ||
} | ||
} | ||
|
||
/** The equal methode of ValueObjects check the value is equal not the reference. */ | ||
public equals(vo: ValueObject<T>): boolean { | ||
if (!TypeGuard.isSameClassTyp(this, vo)) { | ||
return false; | ||
} | ||
|
||
if (TypeGuard.isPrimitiveType(vo.value)) { | ||
return vo.value === this.value; | ||
} | ||
|
||
if (TypeGuard.isArray(vo.value) && TypeGuard.isArray(this.value)) { | ||
return TypeGuard.isShallowEqualArray(this.value, vo.value); | ||
} | ||
|
||
return false; | ||
|
||
/* | ||
VS | ||
|
||
|
||
let isEqual = false; | ||
if (TypeGuard.isPrimitiveType(vo.value)) { | ||
isEqual = vo.value === this.value; | ||
} else if (TypeGuard.isArray(vo.value) && TypeGuard.isArray(this.value)) { | ||
isEqual = TypeGuard.isShallowEqualArray(this.value, vo.value); | ||
} | ||
|
||
return isEqual; */ | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
"of array with primitive values"