Giter VIP home page Giter VIP logo

4lessandrodev / types-ddd Goto Github PK

View Code? Open in Web Editor NEW
241.0 3.0 13.0 6.28 MB

This package provide utils files and interfaces to assistant build a complex application with domain driving design.

Home Page: https://www.npmjs.com/package/types-ddd

License: MIT License

TypeScript 99.65% Shell 0.17% JavaScript 0.18%
typescript-library typescript nodejs-library domain-driven-design clean-architecture ddd ddd-architecture ddd-patterns ddd-example ddd-sample

types-ddd's Introduction

types-ddd

Now version 3.x available

This package provide utils file and interfaces to assistant build a complex application as domain driving design and nodeJS with typescript.


checks stars commits last commit license dependabot tags issues

Install

$ npm i types-ddd

#or 

$ yarn add types-ddd

Lib Full Documentation

Check lib documentation on link Here


image


1. Ubiquitous language:

  • Language and terms agreed upon by both business users and developers, within a bounded context
  • Entities with the same name in a different context can have different behavior and data
  • Bounded context helps in single responsibility for domain models

2. Rich domain model:

  • Models (entities, value objects, aggregates) with rich behavior are preferred over anemic domain models (entities without behavior, which only keep data and represent the DB tables)
  • Due to single responsibility principle (a class or method should have only one reason to change), non-cohesive behavior should be delegated to other classes (or even handled inside domain services) when necessary
  • Model methods can also delegate the task to domain services by raising domain events

3. Thin domain service working on rich domain models:

  • Domain services should not hold state (application services are not domain services, they are on the outer layer close to the UI layer, and can hold application/task state)
  • Domain services have very little behavior and only which does not fit cohesively in any domain model
  • Domain services sit in the core domain layer along with entities, value objects, aggregates and domain events, and expose domain models in their interfaces

4. Layers in a DDD application:

  • Core domain layer (domain services, entities, value objects, aggregates and domain events)
  • Core domain layer is surrounded by the UI/Application layer and Infrastructure layer
  • UI/Application layer (UI and application service facade with messaging, JSON, XML capabilities, session, etc.)
  • Infrastructure layer (persistence, file system, network, mail, logging, etc.)

5. Entities:

  • Live longer than the application, should endure restarts, and are persisted and read from data sources (DB, file system, network, etc.)
  • Have an id (preferably a GUID rather than a DB generated int because business transactions do not rely on persistence, can be persisted after other operations carried out in model's behavior)
  • Have entity semantics (equality and GetHashCode() defined by class name + id)
  • Behavior in an entity mostly orchestrates value objects for a use case
  • Entity class should not have public property setters, setting a property should be a behavior method
  • Entities should not have bidirectional relations (depending on the bounded context, either an egg can have a chicken or a chicken can have eggs, but not both)
  • Entity relations should not reflect the complete set of DB foreign key relationships, should be bare down to the minimum for performing the behavior inside the bounded context
  • Entity relations should not hold a reference to another entity class, it can only keep the id of another entity
  • If a business transaction needs a reference to other entities in relation, aggregates should be used instead (aggregates can hold a reference to other aggregate roots, which are entity classes by definition)

6. Value objects:

  • Are only identified by their values, not by their ids (for example money is a value object as long as we are not tracking individual banknotes, if we need to track individual banknotes then it should be a banknote entity)
  • Can be used to measure or describe things (name, description, amount, height, date, time, range, address, etc.)
  • You can combine other value types that usually go together into a new value object type, like address (city, street, country, postal code) or ...range, or ...type
  • Prefer to put the behavior on value objects rather than on entities because value objects are immutable and do not have side effects (like changing their state or changing the state of any entity)
  • Can be part of an entity
  • Have value semantics (equality and GetHashCode() defined by property values)
  • Should be immutable, behaviors should not change the state of a value object, but can rather create a new value object (should act similar to C# strings, structs, ints, and other value types)
  • Can be persisted but only as part of an entity, not individually

7. Factories:

  • Create, build aggregates and entities:
  • Static Create...() factory method on a model class is used to guard against the construction of an invalid or incomplete model
  • The model class should not have a public default constructor (however if it is to be persisted, for Entity Framework to work, it can have a protected or private default constructor)

8. Aggregates:

  • Encapsulate and are composed of entity classes and value objects that change together in a business transaction
  • Aggregates are a transactional graph of model objects
  • Aggregate root should be an entity, an aggregate can even be a single entity
  • Aggregate can keep a reference to other aggregate roots, but not to other entity classes which are not aggregate roots themselves
  • Aggregate should not keep a reference to other aggregate root entity classes if those other entities do not change together with this aggregate root entity
  • Aggregate can also keep the id of another entity, but keeping too many foreign key ids is a code smell (why?)
  • If deleting an entity has a cascade effect on the other entities referenced by class in the object graph, these entities are part of the same aggregate, if not, they should not be inside this aggregate

9. Repositories:

  • Persist and read aggregates to/from DB or file system
  • Should have an interface close to a collection but should allow only the necessary operations needed for this aggregate (for example an aggregate might not need to be allowed to get updated or deleted)
  • Should not be generic (should be specific for the aggregate type)
  • Can have specific query methods if needed (like FindByName() etc.)
  • Do not use lazy loading, instead use eager loading (use Include(...) in Entity Framework), else you can face "N+1 problem"s and excessive number of queries sent to DB
  • Can have specific methods that only load some of the columns from a table
  • Repository add/update/remove operation should commit to DB by itself (call Entity Framework ...Context.SaveChanges() at the end), because aggregate operations should be ACID transactions
  • Repository interface sits inside Core domain layer, but implementations are inside Infrastructure layer
  • Repositories are not used inside the domain models (entities, value objects, aggregates)

10. Shared kernel:

  • Is where cross-cutting concerns or common types shared by all bounded contexts sit (like entity abstract base type, value object abstract base type, common value objects, authorization, etc.)

11. Domain events:

  • Can be raised when a state change occurs in an entity
  • Decouple models from each other
  • Only used when an event needs to be handled inside a different model than the one raising this event, or handled inside a domain service or even an application service
  • Are immutable classes, that represent past, named in the past tense, and cannot change (...Changed, ...Happened, etc.)
  • Should include the time that this event was raised, as well as any other useful info for handling the event, as well as the id of the entity which raised the event
  • Should not have behavior
  • Domain events are subscribed to with a callback (lambda), or using pub sub interfaces, on a singleton or static event message bus
  • Domain events implemented this way can be subscribed to and handled in the aggregate root of the entity which raised the event, or in domain services, or even in UI/Application layer
  • Domain events are raised synchronously, if an asynchronous task needs to be carried out, it can be done inside the event handler (async-await pattern)
  • Outside applications can also be triggered by using a message queue or an enterprise service bus (ESB) inside the domain event handler

12. Anti-corruption layer:

  • Used to translate models from outside systems or legacy apps to models inside the bounded context and vice versa, and also to ease the communication with legacy services
  • Can use service facades and model adapters

13 - Summary - Basic Usage

Check full documentation on link Here

Value Object

A value object is a small, simple object that represents a single value or characteristic, such as a monetary amount or a date. It is characterized by having no identity of its own, meaning it is equal to another value object if its values are equal, regardless of its reference. Value objects are often used in domain-driven design to represent simple entities in the system.

Create a value object with business rules.

import { ValueObject, Ok, Fail, Result } from 'types-ddd';

interface Props {
    amount: number;
}

// simple example as monetary value object business behavior
export default class Money extends ValueObject<Props> {
    
    // private constructor. Avoid public new.
    private constructor(props: Props) {
        super(props);
    }

    // any business rule behavior. Check.
    public isGt(x: Money): boolean {
        const { number: Check } = this.validator;
        const xValue = x.get('amount');
        const currentValue = this.get('amount');
        return Check(xValue).isGreaterThan(currentValue);
    }

    // any business rule behavior. Calc.
    public sum(x: Money): Money {
        const { number: Calc } = this.util;
        const value = x.get('amount');
        const current = this.get('amount');
        const amount = Calc(current).sum(value);
        return new Money({ amount });
    }

    // any business rule behavior. Calc.
    public subtract(x: Money): Money {
        const { number: Calc } = this.util;
        const value = x.get('amount');
        const current = this.get('amount');
        const amount = Calc(current).subtract(value);
        return new Money({ amount });
    }

    // any business rule to validate state.
    public static isValidProps({ amount }: Props): boolean {
        const { number: Check } = this.validator;
        return Check(amount).isPositive();
    }

    // shortcut to create a zero value
    public static zero(): Money {
        return new Money({ amount: 0 });
    }

    // factory method to create an instance and validate value.
    public static create(amount: number): Result<Money> {

        const isValid = this.isValidProps({ amount });
        if(!isValid) return Fail("Invalid amount for money");

        return Ok(new Money({ amount }));
    }
}

How to use value object instance

// operation result
const resA = Money.create(500);

// check if provided a valid value
console.log(resA.isOk());

// > true


// money instance
const moneyA = resA.value();

moneyA.get("amount"); 

// 500

// using methods 
moneyA.isGt(Money.zero());

// > true

const moneyB = Money.create(100).value();

const moneyC = moneyA.sum(moneyB);

const value = moneyC.get('amount');

console.log(value); 

// > 600

Entity

An entity in domain-driven design is an object that represents a concept in the real world and has a unique identity and attributes. It is a fundamental building block used to model complex business domains.

Create an entity with business rules.

import { Entity, Ok, Fail, Result, UID } from 'types-ddd';

interface Props {
    id?: UID;
    total: Money;
    discount: Money;
    fees: Money;
}

// simple example as payment entity using money value object
export default class Payment extends Entity<Props> {

    // private constructor
    private constructor(props: Props){
        super(props);
    }

    // any business rule behavior. Update total.
    public applyFees(fees: Money): Payment {
        const props = this.props;
        const total = props.total.sum(fees);
        return new Payment({ ...props, total, fees });
    }

    // any business rule behavior. Discount must be less or equal total.
    public applyDiscount(discount: Money): Payment {
        const props = this.props;
        const total = props.total.subtract(discount);
        return new Payment({ ...props, total, discount });
    }

    // factory method to create a instance. Value must be positive.
    public static create(props: Props): Result<Payment> {
        return Ok(new Payment(props));
    }
}

How to use entity instance

// operation result
const total = Money.create(500).value();
const discount = Money.zero();
const fees = Money.zero();

// create a payment
const payment = Payment.create({ total, discount, fees }).value();

// create fee and discount
const fee = Money.create(17.50).value();
const disc = Money.create(170.50).value();

// apply fee and discount
const result = payment.applyFees(fee).applyDiscount(disc);

// get object from domain entity
console.log(result.toObject());

{
    "id": "d7fc98f5-9711-4ad8-aa16-70cb8a52244a",
    "total": { 
        "amount": 347 
    },
    "discount": {
         "amount": 170.50
    },
    "fees": { 
        "amount": 17.50
    },
    "createdAt":"2023-01-30T23:11:17.815Z",
    "updatedAt":"2023-01-30T23:11:17.815Z"
}

Aggregate

Encapsulate and are composed of entity classes and value objects that change together in a business transaction

Create an aggregate to compose your context.

In my example, let's use the context of payment. All payment transactions are encapsulated by an order (payment order) that represents a user's purchasing context.

import { Aggregate, Ok, Fail, Result, UID, EventHandler } from 'types-ddd';

// Entities and VO that encapsulate context.
interface Props {
    id?: UID;
    payment: Payment;
    items: List<Item>;
    status: OrderStatus;
    customer: Customer;
}

// Simple example of an order aggregate encapsulating entities and 
// value objects for context.
export default class Order extends Aggregate<Props> {

    // Private constructor to ensure instances creation through static methods.
    private constructor(props: Props){
        super(props);
    }

    // Static method to begin a new order. 
    // Takes a customer as parameter and returns an instance of Order.
    public static begin(customer: Customer): Order {
        // Initialize the status of the order as "begin".
        const status = OrderStatus.begin();
        // Initialize the list of items as empty.
        const items: List<Item> = List.empty();
        // Initialize the payment as zero, since the order hasn't been paid yet.
        const payment = Payment.none();
        // Create a new instance of Order with the provided parameters.
        const order = new Order({ status, payment, items, customer });

        // Add an event to indicate that the order has begun.
        order.addEvent('ORDER_HAS_BEGUN', (order) => {
        // Perform some important operation when the order begins.
            console.log('Do something important...');
        });

        // Alternatively, add an event by creating an
        // instance of a class that extends EventHandler.
        order.addEvent(new OrderBeganEventHandler());

        // Return the created order instance.
        return order;
    }

    // Method to add an item to the order. 
    // Takes an item as parameter and returns the Order instance.
    addItem(item: Item): Order {
        // Add the item to the order's items list.
        this.props.items.add(item);
        // Sum item price to payment amount
        this.props.payment.sum(item.price);
        // Return the Order instance itself to allow chained calls.
        return this;
    }

    // Method to perform the payment of the order. 
    // Takes a payment object as parameter.
    pay(payment: Payment): Order {
        // Set the status of the order to "paid".
        this.props.status = OrderStatus.paid();
        // Set the provided payment object.
        this.props.payment = payment;
        // Add an event to indicate that the order has been paid.
        // Assuming OrderPaidEvent is a class representing 
        // the event of order payment.
        this.addEvent(new OrderPaidEventHandler());
        return this; 
    }

    // Static method to create an instance of Order.
    // Returns a Result, which can be Ok (success) or Fail (failure).
    // The value of the Result is an instance of Order, 
    // if creation is successful.
    public static create(props: Props): Result<Order> {
        return Ok(new Order(props));
    }
}

How to use events

Event Handler

import { Context, EventHandler } from 'rich-domain';


class OrderCreatedEvent extends EventHandler<Order> {

    constructor() {
        super({ eventName: 'OrderCreated' });
    }

    dispatch(order: Order): void {
        // dispatch event to another context
        order.context().dispatchEvent('Context:Event', order.toObject());
    };
}

Aggregates domain events

order.addEvent('Event', (...args) => {
    console.log(args);
});

// Or add an EventHandler instance
order.addEvent(new OrderCreatedEvent());

order.dispatchEvent('OrderBegun');

// dispatch with args
order.dispatchEvent('Event', { info: 'custom_args' });

// OR call all added events
await order.dispatchAll();

How to subscribe to a global event

import { Context } from 'rich-domain';

const context = Context.events();

context.subscribe('Context:Event', (event) => {
   const [model] = event.detail;
   console.log(model);
});

// dispatch an event to a context with args
context.dispatchEvent('Context:Event', { name: 'Jane' });

// Dispatching events to specific contexts
// Dispatches the SIGNUP event to Context-X
context.dispatchEvent('Context-X:Signup'); 

// Dispatches the SIGNUP event to all contexts
context.dispatchEvent('*:Signup'); 

// Dispatches all events to all contexts. Not recommended
context.dispatchEvent('*:*'); 

// Dispatches all events under Context-Y
context.dispatchEvent('Context-Y:*'); 

Lib Full Documentation

Check lib documentation on link Here

types-ddd's People

Contributors

4lessandrodev avatar arturhamannronconi avatar dependabot[bot] avatar mark01zlatar avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

types-ddd's Issues

Error on toObject method from value object

system: linux
types-ddd version: 2.9.13
node-version: 16.13

on convert value object like example below

interface Props {
  budgetBoxId: DomainId;
  currency: CurrencyValueObject;
}

export class ExampleValueObject extends ValueObject<Props> {
	private constructor (props: Props) {
		super(props);
	}

	get budgetBoxId (): DomainId {
		return this.props.budgetBoxId;
	}

	get currency (): CurrencyValueObject {
		return this.props.currency;
	}

	public static create (props: Props): Result<ExampleValueObject> {
		return Result.ok<ExampleValueObject>(new ExampleValueObject({ ...props }));
	}
}

convert to object

const vo = ExampleValueObject.create({
   budgetBoxId: DomainId.create("valid_budgetId"),
   currency: CurrencyValueObject.create({
       value: 200,
       currency: "BRL"
   }).getResult()
}).getResult();

console.log(vo.toObject());

result

    {
      "budgetBoxId": {
        "props": {
          "value": "valid_budgetId"
        },
        "isNew": false
      },
      "currency": {
        "currency": "BRL",
        "value": 200
      }
    }

expected result

    {
      "budgetBoxId":  "valid_budgetId",
      "currency": {
        "currency": "BRL",
        "value": 200
      }
    }

Invalid Email - EmailVallueObject

on create email value object handle error bellow

ERROR [17:40:20]: ERROR 37665 Can not get the value of an error result. Use errorValue instead.
ERROR [17:40:20]: ERROR 37665 {"statusCode":"UNPROCESSABLE_ENTITY","statusCodeNumber":422,"isSuccess":false,"isFailure":true,"error":"Invalid email","_value":null}

Value Provided: " [email protected]""

All emails with dot fails

Create shortcuts for imports

Before

import { Result } from "types-ddd/dist/core/result";
import { AggregateRoot } from "types-ddd/dist/core/entity";
import { BaseDomainEntity } from "types-ddd/dist/core/base-domain-entity";

After

import { Result, AggregateRoot, BaseDomainEntity } from "types-ddd/dist/core";

Guidance: Domain Events

I love this lib. Thank you for creating it 🙏

Is your feature request related to a problem? Please describe.
'm stuck on handling domain events. Can you provide some guidance on how to set up subscriptions? Specifically subscribing to events across contexts.

Describe the solution you'd like
Any of the following:

  • Documentation of subscriptions
  • Updated example code
  • written response

Describe alternatives you've considered
I've applied my knowledge of DDD, the documentation and your example project to build my own app. I can't quite work it out. iHandle, IDomainEvent, DomainEvents, DomainHandler, Command, bit lost.

Additional context
This guidance will really complement the fantastic work you've already done. You rock!

Proposal: Splitting Components (Value Objects) into Individual Packages

Currently

the types-ddd library provides several components (value objects) in a single package. However, it would be beneficial for users if these components were made available in individual packages, allowing users to install only the components they actually need.

For example, one user might only need the cpf component, while another might only need the cnpj component. Allowing the individual installation of each component would provide a more flexible and lean experience for users.

I suggest that the types-ddd library be split into individual packages for each component (value object), so that users can install all components or just the specific ones they need. This would also make maintenance and evolution of each component separately easier.

Proposal:

Split the types-ddd library into individual packages for each component (value object).
Update the documentation to reflect this change and provide clear instructions on how to install the components individually or all at once.

Usage Example:

# Installation of all value-objects
npm install @types-ddd/core

# Installation of a specific value object (example: money)
npm install @types-ddd/currency

This division will allow users to have more control over which package they want to use, reducing the installation size and improving the flexibility of using the types-ddd library.

Assistance & Congrats 🤚🏻 🎉

Hi @4lessandrodev , congrats for this awesome project!

I spent some time evaluating alternatives to use an utility library to develop DDD in TypeScript and this is amazing!

I open this issue to congratulate you on your work and offer you a little help if you need it in any area (new features, debugging, documentation ...)

Congratulations 🎉 !

Error on convert complex value object from an entity

Error on convert complex value object from an entity
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

regarding toObject function, if we take a look at auto-mapper.spec.ts file:

when we add any primitive value to props(e.g isPublicProfile: boolean) that property would be undefined
also, seems that if we have complex value-object, value would be skipped/undefined

	interface SocialInfoVOProps {
		links: string[],
		publicEmail?: string
	}
	
	class SocialInfoVO extends ValueObject<SocialInfoVOProps> {
		public static MAX_LINKS = 5;

		constructor(props: SocialInfoVOProps) {
			super(props)
		}

		get links() {
			return this.props.links
		}
		get publicEmail() {
			return this.props.publicEmail
		}
		
		public static create(props: { links: string[], email: string }): Result<SocialInfoVO> {
			//... validate and do stuff
			return Result.ok(new SocialInfoVO(...)) 
		}
	}

seems that value-object conversion needs to be changed/improved

Expected behavior
this value object must result like example below:

{
  links:[ "..." ],
  publicEmail: "..."
}

Desktop

  • OS: Linux
  • Version 20.04

When the return type is "void", a type error occurs on get the error message

example

import { Result } from 'types-ddd';

const printNumber = (value: number): Result<void> => {
   if(value < 0) {
      return Result.fail<void>('Value must be positive');
   }
   console.log(value);
   return Result.ok<void>();
}

const spectError = printNumber(-10);

throw new Error(spectError.error) // error return is void or string 

Entity Creation - New Approach

Entities

New way to create an entity, value object and aggregate

import {  DomainEntity, ValueObject, Entity, DomainId , ID } from '@types-ddd/core';

@DomainEntity({ name: 'User' })
export class UserEntity extends Entity {
   
    @DomainId
    id: ID;
    
    @ValueObject({ name: 'userName', required: true, Length:{ max: 30, min: 3}, type: string })
    userName: UserNameValueObject;
    
    @ValueObject({ name: 'age', type: number, max: 120, min: 1 })
    age: AgeValueObject
    
    @DomainEntity({ type: Array<PostEntity>, required: false, default: [] })
    posts: PostEntity[];

}

References:

  1. Class Transformer
  2. Entity Decorators
  3. Typeorm Entity

Implement CPFValueObject as util Value Object

CPF stands for Cadastro de Pessoas Físicas (Natural Persons Register). Native Brazilians can request a CPF at any time in their lives - parents will often apply on behalf of their children when they are newborns, as CPFs are vital to living in Brazil and it’s best to obtain one sooner rather than later.

A CPF is 11 digits long, comprised of nine base digits, and two digits at the end that are the result of an arithmetic operation on the first nine numbers, meaning any typing mistakes will lead to an invalid number. Here is an example of a CPF: 231.002.999-00.

Implement comparation method on instance of DomainId

import { DomainId } from 'types-ddd';

const userIdA = DomainId.create(); // random uuid
const userIdB = DomainId.create(); // random uuid

// Implement comparation 

const isEqualA = userIdA.equal(userIdB); 
console.log(isEqualA);
> false

const userIdC = DomainId.create("custom_valid_uuid"); // defined uuid
const userIdD = DomainId.create("custom_valid_uuid"); // defined uuid

// Implement comparation 

const isEqualB = userIdC.equal(userIdD); 
console.log(isEqualB);
> true

function removeUndefinedKeysFromObject is removing dates

{
  id: '0e443d84-c92b-44d0-86af-dacb7167fbc2',
  description: 'Description',
  championshipType: {
    id: '0dd17ac3-7520-4aec-bc9e-70d9d4881d2b',
    description: 'description value',
    name: 'name',
    createdAt: 2021-10-09T05:56:09.286Z,
    updatedAt: 2021-10-09T05:56:09.286Z
  },
  eventOccursAt: 2022-01-01T00:00:00.000Z,
  game: {
    id: 'f62ac1e1-372d-4c75-8ea1-a2ea08985152',
    createdBy: 'c9503168-9ce8-43bd-9542-b25d9e890601',
    name: 'grand theft auto vice city',
    rating: 'EIGHTEEN',
    description: 'value',
    isActive: true,
    createdAt: 2021-10-09T05:56:00.995Z,
    updatedAt: 2021-10-09T05:56:00.995Z,
    image: undefined
  },
  ownerId: 'c9503168-9ce8-43bd-9542-b25d9e890601',
  maxNumberOfParticipants: 20,
  maxNumberOfParticipantsPerTeam: 2,
  participants: [],
  positions: [],
  status: 'DRAFT',
  totalPremiumPayable: { currency: 'BRL', value: 0 },
  subscriptionPrice: { currency: 'BRL', value: 20 },
  totalCollected: { currency: 'BRL', value: 0 },
  createdAt: 2021-10-09T06:14:05.547Z,
  updatedAt: 2021-10-09T06:14:05.547Z
}

Result after function

{
  id: 'ca1268a2-8eff-4d93-ac1a-0a6d6b5e9953',
  description: 'Description',
  championshipType: {
    id: '0dd17ac3-7520-4aec-bc9e-70d9d4881d2b',
    description: 'description value',
    name: 'name'
  },
  game: {
    id: 'f62ac1e1-372d-4c75-8ea1-a2ea08985152',
    createdBy: 'c9503168-9ce8-43bd-9542-b25d9e890601',
    name: 'grand theft auto vice city',
    rating: 'EIGHTEEN',
    description: 'value',
    isActive: true
  },
  ownerId: 'c9503168-9ce8-43bd-9542-b25d9e890601',
  maxNumberOfParticipants: 20,
  maxNumberOfParticipantsPerTeam: 2,
  participants: [],
  positions: [],
  status: 'DRAFT',
  totalPremiumPayable: { currency: 'BRL', value: 0 },
  subscriptionPrice: { currency: 'BRL', value: 20 },
  totalCollected: { currency: 'BRL', value: 0 }
}

Bug trying to implement an ObjectValue in NextJs

Describe the bug
When i try to create a an ObjectValue it throw an error.
Module not found: Can't resolve 'fs'

To Reproduce
ObjectValue

import { IResult, Result, ValueObject } from 'types-ddd';

export interface NameProps {
  value: string;
}

export class Name extends ValueObject<NameProps> {
  private constructor(props: NameProps) {
    super(props);
  }

  public static create(props: NameProps): IResult<Name> {
    return Result.Ok(new Name(props));
  }
}

export default Name;

const Home: NextPage = () => {
  Name.create({value : 'tamon'})

  return (
    <div className={styles.container}>
    </div>
  );
};

Error Thrown
Screen Shot 2022-09-14 at 6 05 32

Add Clean-Code Tooling

Is your feature request related to a problem? Please describe.

  • Clean-code tooling would help with library/project code quality and consistency.
  • It would simplify the development and contribution process

Describe the solution you'd like
Add dev tools/libs like:

Flow:

  • pre-commit hook: run formatter/linters/ and check circular dependencies
  • pre-push hook: run tests

Notes

  • Prettier is an opinionated code formatter so we could check for alternatives

validate aggregate state to custom types

Consider validate others types

Currently

!this.checkProps( ['userId', 'teamColor'] ).isSome( 'undefined' );

Proposed

!this.checkProps( ['userId', 'teamColor'] ).isSome('undefined').or(null);
!this.checkProps( ['userId', 'teamColor'] ).isType(null);

types-ddd-cli

Types-ddd cli

Build a cli to auto generate domain files

Requires npm and nodejs installed

Example how it will works

A cli to works with types-ddd lib. A domain driven design library.

Initial resources

  • value-object
  • entity
  • aggregate
  • mapper
  • domain-event

Installation

	$ npm i types-ddd-cli -g

Help

	$ types-ddd --help

OR

	$ types-ddd help

Generate

	$ types-ddd create value-object --name example --path ./src/domain/

OR

	$ types-ddd create value-object --name example

Use Error classes in Aggregate

Hey there! First off: thanks for this awesome repository, this and your great sample project helped me a lot.
I have a question though: I prefer using Error classes over pure string messages. I found out that I can basically just use Fail and Ok with these classes. However that doesn't work for Aggregate (I assume same for Entity) as it implements the create method as follows:

static create(props: any): IResult<Aggregate<any>>;

that falls back to the default string parameter D of IResult so the create method doesn't allow me to use anything else. Is this intended or a bug? Maybe I'm using it wrong so some advice would be great here!

export interface IResult<T, D = string, M = {}> {
    value(): T;
    error(): D;
    isFail(): boolean;
    isOk(): boolean;
    metaData(): M;
    toObject(): IResultObject<T, D, M>;
    execute: <X, Y>(command: ICommand<X | void, Y>) => IResultExecute<X, Y>;
}

Cheers

Custom Value Objects - String and Number

const MyCustomValidator = (value: string | number): boolean => {
    return typeof value === 'string' || typeof value === 'number';
}

CustomStringValueObject.create("this is valid", { MAX: 21, MIN: 1, VALIDATOR: MyCustomValidator });

CustomStringValueObject.create("this is an invalid value", { MAX: 5, MIN: 1, VALIDATOR: MyCustomValidator });

CustomStringValueObject.create("this is valid", { MAX: 21, MIN: 1 });

CustomNumberValueObject.create(200, { MAX: 5, MIN: 1, VALIDATOR: MyCustomValidator });

CustomNumberValueObject.create(2, { MAX: 5, MIN: 1, VALIDATOR: MyCustomValidator });

CustomNumberValueObject.create(2, { MAX: 5, MIN: 1, });

Implement ItemDimensionsValueObject as util Value Object

create dimensions for an item

Weight: Weight Unit VO
Height: Unit of Measure VO
Width: Unit of Measure VO
Length: Unit of Measure VO

Weight Unit: Enum = Kg / Ton / g / Lb
Unit of Measure for Length: Enum = Cm / Mm / M / In

(Assistance) Minor improvements

Before everything:

Just want to say thanks for the lib, I believe you did a great job here.

Short Background:

Been working with Typescript for about 2 years now. For the last 3-4 months I've been playing with DDD in typescript, learning about DDD, and was basically implementing something similar you have here(note: you did a much better job in every aspect 🙂 ).

For the last few days, I was playing with this lib, looking at source code, and implementing types-ddd to the existing project(to see how things are going to play).

About the project(won't go too much in details):

  • I implemented a serverless microservice event-driven architecture and so far it's going well.
  • Hosting is done on AWS (CDK IaC)
  • Because of AWS lambda usage(serverless architecture), there we difficulties that I had to overcome and that I'm still working on...

Small Changes/Features Proposals

All changes are around the same things basically :)

  1. Friendly Imports - better Barrel exports
    Currently If i don't want to import whole lib, but to use individual modules(e.g only Core and Repo) i have to do something like:
import { Result } from "types-ddd/dist/core/result";
import { AggregateRoot } from "types-ddd/dist/core/entity";
import { BaseDomainEntity } from "types-ddd/dist/core/base-domain-entity";

You did a good job with modules organization, so with a few simple changes we could have something like:

import { Result, AggregateRoot, BaseDomainEntity } from "types-ddd/dist/core";

or we could even think about better organization in general.. for now, this would be good enough(build would be much smaller and imports would be simpler)
2. Decoupling - making utils optional and decoupled from other modules, removing dependencies(e.g bcrypt)
Installing this lib made the project from found 0 vulnerabilities to 2 vulnerabilities (1 moderate, 1 high) even though 0 dependencies were used in the project(both auth and logs we already implemented)..
After digging I found out that "bcrypt" was the issue, this was solved by previously mentioned imports..
3. Lib Logging
Instead of having pino + pino-pretty that log everything constantly we could use debug. Separate logs per module/feature/component and then show them only explicitly when needed.
Note that pino-debug also exists.
3. toObject without mapper -> autoConvertDomainToObject
Saw that this was in beta and after testing found out that the results were not as expected.. even though I wrote getters on aggregate after conversion some properties were undefined (I'm probably missing something and I'll have to dig into the source more to understand what's going on).
Ultimately, the feature would speed up the process(conversion/mapping) so I'm looking forward to id 🙂

Final thoughts:

  • I know that DDD is slow/bulky to implement and that serverless architecture maybe not be the best choice... For now, it plays extremely well but we will see it as the project scale, let me know what do you think(and thanks in advance).
  • New approach for the entity creation looks really nice, would love to support you on this
  • So far codebase seems great and would love to contribute

Also note: If I continue to use this lib and the project goes well, you can expect donations for sure..

Errors while running tests

Hi,

i'm trying to run tests on your repo but i get the following errors :

FAIL  tests/domain-id.spec.ts
  ● domain-id › should create a new uuid as id

    TypeError: crypto_1.randomUUID is not a function

       8 | export default class UniqueEntityID extends Identifier<string | number> {
       9 |      constructor (id?: string | number) {
    > 10 |              super(id ? id : randomUUID());
         |                              ^
      11 |      }
      12 | }
      13 |

      at new UniqueEntityID (src/core/unique-entity-id.ts:10:19)
      at Function.create (src/core/domain-id.ts:32:31)
      at Object.<anonymous> (tests/domain-id.spec.ts:10:31)

Test Suites: 5 failed, 8 passed, 13 total

I do not undestand what's going on, can you help me please ?

Invalid Email - EmailValueObject

EmailValueObject handle error bellow

[05:35:55] ERROR: ERROR 27688 Can not get the value of an error result. Use errorValue instead.
[05:35:55] ERROR: ERROR 27688 {"statusCode":"UNPROCESSABLE_ENTITY","statusCodeNumber":422,"isSuccess":false,"isFailure":true,"error":"Invalid email","_value":null}

Value Provided: "[email protected]"

Dúvida, PasswordValueObject

@4lessandrodev, eu procurei na doc porem não achei sobre a validação, eu vi que eu posso usar validation(value: string): boolean; porem vi que ele valida 5 caracteres, se eu precisar aumentar esse valor para 8 por exemplo, qual seria o melhor caminho?

Uma outra duvida que surgiu, qual a diferença desse repositório type-ddd e o repositório rich-domain?

use Error classes in Adapter

Hey dude,

similar to #272 I can only use strings as Result errors when using an Adapter. That happens because IAdapter only forwards T to IResult so the error argument defaults to string.

export interface IAdapter<F, T> {
    build(target: F): IResult<T>;
}

would be great if I could use an error class here as well.

Cheers!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.