https://github.com/awakelife93/ddd-with-specification-pattern
DDD Architecture + Specification pattern
https://github.com/awakelife93/ddd-with-specification-pattern
ddd ddd-architecture design-patterns nodejs spec specification specification-by-example specification-pattern
Last synced: 3 months ago
JSON representation
DDD Architecture + Specification pattern
- Host: GitHub
- URL: https://github.com/awakelife93/ddd-with-specification-pattern
- Owner: awakelife93
- License: mit
- Created: 2023-12-15T07:23:45.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2023-12-17T09:33:51.000Z (over 2 years ago)
- Last Synced: 2025-01-19T09:12:05.770Z (about 1 year ago)
- Topics: ddd, ddd-architecture, design-patterns, nodejs, spec, specification, specification-by-example, specification-pattern
- Language: TypeScript
- Homepage:
- Size: 15.6 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
### Example DDD + Specification Design Pattern
This project is based on the DDD + Specification pattern.\
Add as many comparison logics as necessary for various logics or domains at the abstract level.
### [Demo](example.ts)
The example only uses **AND logic**, but **OR** and **NOT** can also be used if necessary.
- npm i or npm install
- npm run example
```typescript
const currentProduct = new Product("book", 1000, 0);
const currentOrder = new Order(currentProduct, 1, 1000);
const wrongProduct = new Product("book", 0, 0);
const wrongOrder = new Order(wrongProduct, 0, 0);
const productPriceMustBeGreaterZeroSpecification =
new ProductPriceMustBeGreaterZeroSpecification();
const productNameIsNotBlankSpecification =
new ProductNameIsNotBlankSpecification();
const productDiscountWithinLimitSpecification =
new ProductDiscountWithinLimitSpecification();
const orderPriceConsistencySpecification =
new OrderPriceConsistencySpecification();
const orderQuantityMustBeGreaterZeroSpecification =
new OrderQuantityMustBeGreaterZeroSpecification();
const productAndSpecification = productPriceMustBeGreaterZeroSpecification
.and(productNameIsNotBlankSpecification)
.and(productDiscountWithinLimitSpecification);
const orderAndSpecification = orderPriceConsistencySpecification.and(
orderQuantityMustBeGreaterZeroSpecification
);
// true
const currentProductOutput =
productAndSpecification.isSatisfiedBy(currentProduct);
console.log(`currentProductOutput = ${currentProductOutput}`);
// true
const currentOrderOutput = orderAndSpecification.isSatisfiedBy(currentOrder);
console.log(`currentOrderOutput = ${currentOrderOutput}`);
// false
const wrongProductOutput = productAndSpecification.isSatisfiedBy(wrongProduct);
console.log(`wrongProductOutput = ${wrongProductOutput}`);
// false
const wrongOrderOutput = orderAndSpecification.isSatisfiedBy(wrongOrder);
console.log(`wrongOrderOutput = ${wrongOrderOutput}`);
```
### [Specification](Specification.ts)
This project is composed of and, or, and not.
```typescript
interface Specification {
isSatisfiedBy(candidate: T): boolean;
}
abstract class CompositeSpecification implements Specification {
abstract isSatisfiedBy(candidate: T): boolean;
and(specification: CompositeSpecification): CompositeSpecification {
return new AndSpecification(this, specification);
}
or(specification: CompositeSpecification): CompositeSpecification {
return new OrSpecification(this, specification);
}
not(): NotSpecification {
return new NotSpecification(this);
}
}
```
### Example Domain & Specification
- [Order](domain/order/Order.ts)
- OrderPriceConsistencySpecification
- OrderQuantityMustBeGreaterZeroSpecification
```typescript
/**
* @class OrderPriceConsistencySpecification
* @description
* The order price cannot exceed or be less than the total sum of the product price and discount price.
*/
class OrderPriceConsistencySpecification extends CompositeSpecification {
isSatisfiedBy(candidate: Order): boolean {
if (!candidate) return false;
const totalPrice =
candidate.quantity *
(candidate.product.price - candidate.product.discountPrice);
return candidate.totalPrice === totalPrice;
}
}
/**
* @class OrderQuantityMustBeGreaterZeroSpecification
* @description
* This specification ensures that the order quantity must be greater than zero.
*/
class OrderQuantityMustBeGreaterZeroSpecification extends CompositeSpecification {
isSatisfiedBy(candidate: Order): boolean {
if (!candidate) return false;
return candidate.quantity > 0;
}
}
```
- [Product](domain/product/Product.ts)
- ProductPriceMustBeGreaterZeroSpecification
- ProductNameIsNotBlankSpecification
- ProductDiscountWithinLimitSpecification
```typescript
/**
* @class ProductPriceMustBeGreaterZeroSpecification
* @description
* The price of the product must always be greater than 0.
*/
class ProductPriceMustBeGreaterZeroSpecification extends CompositeSpecification {
isSatisfiedBy(candidate: Product): boolean {
if (!candidate) return false;
return candidate.price > 0;
}
}
/**
* @class ProductNameIsNotBlankSpecification
* @description
* The product name cannot be blank.
*/
class ProductNameIsNotBlankSpecification extends CompositeSpecification {
isSatisfiedBy(candidate: Product): boolean {
if (!candidate) return false;
return candidate.name.length > 0;
}
}
/**
* @class ProductDiscountWithinLimitSpecification
* @description
* The discounted price of a product cannot be greater than the normal price.
*/
class ProductDiscountWithinLimitSpecification extends CompositeSpecification {
isSatisfiedBy(candidate: Product): boolean {
if (!candidate) return false;
return candidate.discountPrice < candidate.price;
}
}
```
### Author
Hyunwoo Park