All Projects → almin → Ddd Base

almin / Ddd Base

Licence: mit
DDD base class library for JavaScript application.

Programming Languages

javascript
184084 projects - #8 most used programming language
typescript
32286 projects

Labels

Projects that are alternatives of or similar to Ddd Base

nstate
A simple but powerful react state management library with low mind burden
Stars: ✭ 11 (-83.33%)
Mutual labels:  ddd, class
Valueobjects
A PHP library/collection of classes / immutable objects!
Stars: ✭ 49 (-25.76%)
Mutual labels:  ddd
Mcdowell Cv
A Nice-looking CV template made into LaTeX
Stars: ✭ 855 (+1195.45%)
Mutual labels:  class
Bank api
Code from the Event Sourcing With Elixir blog series
Stars: ✭ 35 (-46.97%)
Mutual labels:  ddd
Wolkenkit
wolkenkit is an open-source CQRS and event-sourcing framework based on Node.js, and it supports JavaScript and TypeScript.
Stars: ✭ 880 (+1233.33%)
Mutual labels:  ddd
Rhodddoobie
My little sandbox for playing around with the FP + OOP + DDD combination, in particular using Rho, doobie, Docker, testing, etc in a project.
Stars: ✭ 38 (-42.42%)
Mutual labels:  ddd
Watimage
🖼 PHP image manipulation class
Stars: ✭ 25 (-62.12%)
Mutual labels:  class
Class Logger
Boilerplate-free decorator-based class logging
Stars: ✭ 64 (-3.03%)
Mutual labels:  class
Event Sourcing With Kotlin
A demo project to show Event Sourcing with Kotlin.
Stars: ✭ 47 (-28.79%)
Mutual labels:  ddd
Freedom
Freedom是一个基于六边形架构的框架,可以支撑充血的领域模型范式。
Stars: ✭ 972 (+1372.73%)
Mutual labels:  ddd
Eventhorizon
CQRS/ES toolkit for Go
Stars: ✭ 961 (+1356.06%)
Mutual labels:  ddd
Nbem
💎 nbem is for intuitively write the class name of BEM method. React and others.
Stars: ✭ 28 (-57.58%)
Mutual labels:  class
Ultimate Backend
Multi tenant SaaS starter kit with cqrs graphql microservice architecture, apollo federation, event source and authentication
Stars: ✭ 978 (+1381.82%)
Mutual labels:  ddd
Structvsclassperformance
POC for my Medium article
Stars: ✭ 11 (-83.33%)
Mutual labels:  class
Lectures
Lecture scripts and slides I use during the Software Engineering course at TU Dresden
Stars: ✭ 52 (-21.21%)
Mutual labels:  ddd
Es6 Class Bind All
Automaticlly bind 'this' scope of all methods in es6 class
Stars: ✭ 10 (-84.85%)
Mutual labels:  class
Practical Clean Ddd
A simplified and effortless approach to get started with Domain-driven Design, Clean Architecture, CQRS, and Microservices patterns
Stars: ✭ 28 (-57.58%)
Mutual labels:  ddd
Ddd Dynamic
Domain Driven Design in Python, Ruby and other dynamic languages resources
Stars: ✭ 973 (+1374.24%)
Mutual labels:  ddd
Context Mapper Examples
ContextMapper DSL: Examples
Stars: ✭ 66 (+0%)
Mutual labels:  ddd
Ddd Symfony Sample
A symfony sample application using DDD
Stars: ✭ 58 (-12.12%)
Mutual labels:  ddd

ddd-base Build Status

Status: Experimental

DDD base class library for JavaScript client-side application.

Notes:

This library does not depend on Almin. You can use it with other JavaScript framework.

Features

This library provide basic DDD base classes.

  • Entity: Entity is domain concept that have a unique identity
  • Identifier: Identifier is unique identifier for an Entity
  • ValueObject: Value Object is an entity’s state, describing something about the entity
  • Repository: Repository is used to manage aggregate persistence
  • Converter: Converter convert between Entity <-> Props(Entity's props) <-> JSON(Serialized data object)

Install

Install with npm:

npm install ddd-base

Usage

Entity

Entities are domain concepts that have a unique identity in the problem domain.

Entity's equability is Identifier.

Identifier

Identifier is a unique object for each entity.

Entity#equals check that the Entity's identifier is equaled to other entity's identifier.

Entity Props

Entity Props have to id props that is instance of Identifier.

  1. Define XProps type
    • XProps should include id: Identifier<string|number> property.
class XIdentifier extends Identifier<string> {}
interface XProps {
    id: XIdentifier; // <= required
}
  1. Pass XProps to Entity<XProps>
class XEntity extends Entity<XProps> {
    // implement
}

You can get the props via entity.props.

const xEntity = new XEntity({
    id: new XIdentifier("x");
});
console.log(xEntity.props.id);

Example:

// Entity A
class AIdentifier extends Identifier<string> {}
interface AProps {
    id: AIdentifier;
}
class AEntity extends Entity<AProps> {}
// Entity B
class BIdentifier extends Identifier<string> {}
interface BProps {
    id: BIdentifier;
}
class BEntity extends Entity<BProps> {}
// A is not B
const a = new AEntity({
    id: new AIdentifier("1"))
});
const b = new BEntity({
    id: new BIdentifier("1")
});
assert.ok(!a.equals(b), "A is not B");

Props can includes other property.

// Entity A
class AIdentifier extends Identifier<string> {}

interface AProps {
    id: AIdentifier;
    a: number;
    b: string;
}

class AEntity extends Entity<AProps> {
    constructor(props: AProps) {
        super(props);
    }
}

const entity = new AEntity({
    id: new AIdentifier("a"),
    a: 42,
    b: "string"
});

ValueObject

Value object is an entity’s state, describing something about the entity or the things it owns.

ValueObject's equability is values.

import {ValueObject} from "ddd-base";

// X ValueObject
type XProps = { value: number };

class XValue extends ValueObject<XProps> {
    constructor(props: XProps) {
        super(props);
    }
}
// x1's value equal to x2's value
const x1 = new XValue({ value: 42 });
const x2 = new XValue({ value: 42 });
console.log(x1.props.value); // => 42
console.log(x2.props.value); // => 42
console.log(x1.equals(x2));// => true
// x3's value not equal both
const x3 = new XValue({ value: 1 });
console.log(x1.equals(x3));// => false
console.log(x2.equals(x3));// => false

📝 ValueObject's props have not a limitation like Entity. Because, ValueObject's equability is not identifier.

Repository

A repository is used to manage aggregate persistence and retrieval while ensuring that there is a separation between the domain model and the data model.

Repository collect entity.

Currently, Repository implementation is in-memory database like Map. This library provide following types of repository.

  • NonNullableBaseRepository
  • NullableBaseRepository

NonNullableBaseRepository

NonNullableRepository has initial value. In other words, NonNullableRepository#get always return a value.

/**
 * NonNullableRepository has initial value.
 * In other words, NonNullableRepository#get always return a value.
 */
export declare class NonNullableRepository<Entity extends EntityLike<any>, Props extends Entity["props"], Id extends Props["id"]> {
    protected initialEntity: Entity;
    private core;
    constructor(initialEntity: Entity);
    readonly map: MapLike<string, Entity>;
    readonly events: RepositoryEventEmitter<Entity>;
    get(): Entity;
    getAll(): Entity[];
    findById(entityId?: Id): Entity | undefined;
    save(entity: Entity): void;
    delete(entity: Entity): void;
    clear(): void;
}

NullableBaseRepository

NullableRepository has not initial value. In other word, NullableRepository#get may return undefined.

/**
 * NullableRepository has not initial value.
 * In other word, NullableRepository#get may return undefined.
 */
export declare class NullableRepository<Entity extends EntityLike<any>, Props extends Entity["props"], Id extends Props["id"]> {
    private core;
    constructor();
    readonly map: MapLike<string, Entity>;
    readonly events: RepositoryEventEmitter<Entity>;
    get(): Entity | undefined;
    getAll(): Entity[];
    findById(entityId?: Id): Entity | undefined;
    save(entity: Entity): void;
    delete(entity: Entity): void;
    clear(): void;
}

Converter

JSON <-> Props <-> Entity

Converter is that convert JSON <-> Props <-> Entity.

createConverter create Converter instance from Props and JSON types and converting definition.

// Pass Props type and JSON types as generics
// 1st argument is that a Constructor of entity that is required for creating entity from JSON
// 2nd argument is that a mapping object
// mapping object has tuple array for each property.
// tuple is [Props to JSON, JSON to Props]  
createConverter<PropsType, JSONType>(EntityConstructor, mappingObject): Converter<PropsType, JSONType>;

mappingObject has tuple array for each property.

const converter = createConverter<EntityProps, EntityJSON>(Entity, {
    id: [propsToJSON function, jsonToProps function],
    // [(prop value) => json value, (json value) => prop value]
});

Example of createConveter.

// Entity A
class AIdentifier extends Identifier<string> {
}

interface AProps {
    id: AIdentifier;
    a1: number;
    a2: string;
}

class AEntity extends Entity<AProps> {
    constructor(args: AProps) {
        super(args);
    }
}

interface AEntityJSON {
    id: string;
    a1: number;
    a2: string;
}

// Create converter
// Tuple has two convert function that Props -> JSON and JSON -> Props
const AConverter = createConverter<AProps, AEntityJSON>(AEntity, {
    id: [prop => prop.toValue(), json => new AIdentifier(json)],
    a1: [prop => prop, json => json],
    a2: [prop => prop, json => json]
});
const entity = new AEntity({
    id: new AIdentifier("a"),
    a: 42,
    b: "b prop"
});
// Entity to JSON
const json = AConverter.toJSON(entity);
assert.deepStrictEqual(json, {
    id: "a",
    a: 42,
    b: "b prop"
});
// JSON to Entity
const entity_conveterted = converter.fromJSON(json);
assert.deepStrictEqual(entity, entity_conveterted);

📝 Limitation:

Convert can be possible one for one converting.

// Can not do convert following pattern
// JSON -> Entity
// a -> b, c properties

Nesting Converter

You can set Converter instead of mapping functions. This pattern called Nesting Converter.

// Parent has A and B
class ParentIdentifier extends Identifier<string> {
}

interface ParentJSON {
    id: string;
    a: AEntityJSON;
    b: BValueJSON;
}

interface ParentProps {
    id: ParentIdentifier;
    a: AEntity;
    b: BValue;
}

class ParentEntity extends Entity<ParentProps> {
}

const ParentConverter = createConverter<ParentProps, ParentJSON>(ParentEntity, {
    id: [prop => prop.toValue(), json => new ParentIdentifier(json)],
    a: AConverter, // Set Conveter instead of mapping functions
    b: BConverter
});

For more details, see test/Converter-test.ts.

[Deprecated] Serializer

JSON <-> Entity

DDD-base just define the interface of Serializer that does following converting.

  • Convert from JSON to Entity
  • Convert from Entity to JSON

You can implement Serializer interface and use it.

export interface Serializer<Entity, JSON> {
    /**
     * Convert Entity to JSON format
     */
    toJSON(entity: Entity): JSON;

    /**
     * Convert JSON to Entity
     */
    fromJSON(json: JSON): Entity;
}

Implementation:

// Entity A
class AIdentifier extends Identifier<string> {}

interface AEntityArgs {
    id: AIdentifier;
    a: number;
    b: string;
}

class AEntity extends Entity<AIdentifier> {
    private a: number;
    private b: string;

    constructor(args: AEntityArgs) {
        super(args.id);
        this.a = args.a;
        this.b = args.b;
    }

    toJSON(): AEntityJSON {
        return {
            id: this.id.toValue(),
            a: this.a,
            b: this.b
        };
    }
}
// JSON
interface AEntityJSON {
    id: string;
    a: number;
    b: string;
}

// Serializer
const ASerializer: Serializer<AEntity, AEntityJSON> = {
    fromJSON(json) {
        return new AEntity({
            id: new AIdentifier(json.id),
            a: json.a,
            b: json.b
        });
    },
    toJSON(entity) {
        return entity.toJSON();
    }
};

it("toJSON: Entity -> JSON", () => {
    const entity = new AEntity({
        id: new AIdentifier("a"),
        a: 42,
        b: "b prop"
    });
    const json = ASerializer.toJSON(entity);
    assert.deepStrictEqual(json, {
        id: "a",
        a: 42,
        b: "b prop"
    });
});

it("fromJSON: JSON -> Entity", () => {
    const entity = ASerializer.fromJSON({
        id: "a",
        a: 42,
        b: "b prop"
    });
    assert.ok(entity instanceof AEntity, "entity should be instanceof AEntity");
    assert.deepStrictEqual(
        ASerializer.toJSON(entity),
        {
            id: "a",
            a: 42,
            b: "b prop"
        },
        "JSON <-> Entity"
    );
});

📝 Design Note

Why entity and value object has props?

It come from TypeScript limitation. TypeScript can not define type of class's properties.

// A limitation of generics interface
type AEntityProps = {
  key: string;
}
class AEntity extends Entity<AEntityProps> {}

const aEntity = new AEntity({
  key: "value"
});
// can not type
aEntity.key; // type is any?

We can resolve this issue by introducing props property.

// `props` make realize typing
type AEntityProps = {
  key: string;
}
class AEntity extends Entity<AEntityProps> {}

const aEntity = new AEntity({
  key: "value"
});
// can not type
aEntity.props; // props is AEntityProps

This approach is similar with React.

Nesting props is ugly

If you want to access nested propery via props, you have written a.props.b.props.c. It is ugly syntax.

Instead of this, you can assign props values to entity's properties directly.

class ShoppingCartItemIdentifier extends Identifier<string> {
}

interface ShoppingCartItemProps {
    id: ShoppingCartItemIdentifier;
    name: string;
    price: number;
}

class ShoppingCartItem extends Entity<ShoppingCartItemProps> implements ShoppingCartItemProps {
    id: ShoppingCartItemIdentifier;
    name: string;
    price: number;

    constructor(props: ShoppingCartItemProps) {
        // pass to props
        super(props);
        // assign own property
        this.id = props.id;
        this.name = props.name;
        this.price = props.price;
    }
}

const item = new ShoppingCartItem({
    id: new ShoppingCartItemIdentifier("item 1");
    name: "bag";
    price: 1000
});

console.log(item.props.price === item.price); // => true

props is readonly by default

This is related with "Nesting props is ugly"

props is readonly and Object.freeze(props) by default.

props:

It is clear that props are a Entity's configureation. They are received from above and immutable as far as the Entity receiving them is concerned.

state:

ddd-base does not define state type. But, state is own properties of Entity. It is mutable value and it can be modified by default.

For example, this.id, this.name, and this.price are state of ShoppingCartItem. You can modify this state.

class ShoppingCartItemIdentifier extends Identifier<string> {
}

interface ShoppingCartItemProps {
    id: ShoppingCartItemIdentifier;
    name: string;
    price: number;
}

class ShoppingCartItem extends Entity<ShoppingCartItemProps> implements ShoppingCartItemProps {
    id: ShoppingCartItemIdentifier;
    name: string;
    price: number;

    constructor(props: ShoppingCartItemProps) {
        // pass to props
        super(props);
        // assign own property
        this.id = props.id;
        this.name = props.name;
        this.price = props.price;
    }
}

Changing props and state

props state
Can get initial value from parent Entity? Yes Yes
Can be changed by parent Entity? Yes No
Can set default values inside Entity? Yes Yes
Can change inside Entity? No Yes
Can set initial value for child Entity? Yes Yes

Related concept:

Real UseCase

Changelog

See Releases page.

Running tests

Install devDependencies and Run npm test:

npm i -d && npm test

Contributing

Pull requests and stars are always welcome.

For bugs and feature requests, please create an issue.

  1. Fork it!
  2. Create your feature branch: git checkout -b my-new-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin my-new-feature
  5. Submit a pull request :D

Author

License

MIT © azu

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].