Skip to content
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

Entity/ValueObject: receive Props Instead of Identifier #4

Open
azu opened this issue Apr 30, 2018 · 5 comments
Open

Entity/ValueObject: receive Props Instead of Identifier #4

azu opened this issue Apr 30, 2018 · 5 comments

Comments

@azu
Copy link
Member

azu commented Apr 30, 2018

I've noticed that I often write a entity like following:

export class HatebuIdentifier extends Identifier<string> {}
export interface HatebuProps {
    readonly id: HatebuIdentifier;
    readonly bookmark: Bookmark;
}

export class Hatebu extends Entity<HatebuIdentifier> implements HatebuProps {
    readonly bookmark: Bookmark;

    constructor(args: HatebuProps) {
        super(args.id);
        this.bookmark = args.bookmark;
    }

    get name() {
        return this.id.toValue();
    }

    addBookmarkItems(bookmarkItems: BookmarkItem[]) {
        return new Hatebu({
            ...(this as HatebuProps),
            bookmark: this.bookmark.addBookmarkItems(bookmarkItems)
        });
    }

    updateBookmarkItems(bookmarkItems: BookmarkItem[]) {
        return new Hatebu({
            ...(this as HatebuProps),
            bookmark: this.bookmark.updateBookmarkItems(bookmarkItems)
        });
    }
}

This Hatebu entity has id and Props type.
We can use Props type insteadof Id.
Becaseue, Alwasys Props includes id.

Props

Cons

  • id is fixed name
  • Always arguments is object
@azu
Copy link
Member Author

azu commented Apr 30, 2018

TypeScript can not implements generics type.
We can just use constructor(prop: Props) that is similar with React.

type Props = {
  id: Identifier<string>
}
class MyEntity extends Entity<Props>{}

azu added a commit that referenced this issue May 6, 2018
BREAKING CHANGE: This change require props object always on Entity

#4
@azu azu changed the title Entity/ValueObject: accept Props Instead of Identifier Entity/ValueObject: receive Props Instead of Identifier May 6, 2018
@azu
Copy link
Member Author

azu commented May 20, 2018

azu/hatebupwa#11

I've tried to apply this pattern.
There are props and cons.

Props

  • Props merging is easy
         return new AppSession({
-            ...(this as AppSessionProps),
+            ...this.props
+            hatebuId: hatebu.props.id
         });

Cons

  • Accessing to nest entity props is hard.

This is ugly. :(

-        const lastUpdatedDate = hatebu.bookmark.lastUpdated;
+        const lastUpdatedDate = hatebu.props.bookmark.props.lastUpdated;

We can avoid this cons to define getter function to entity.
But, It will increase code.

@azu
Copy link
Member Author

azu commented May 20, 2018

x.props.y.props.z is unly.
But, we can avoid this by assign props to properties manually.
Compare 0.4 between 0.5 is very similar code.

props is immutable value. It will be changed in constructor.
own property like state is mutable value. It will be changed in anywhere.

import { Entity, Identifier, ValueObject } from "../src";
import * as assert from "assert";


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) {
        super(props);
        this.id = props.id;
        this.name = props.name;
        this.price = props.price;
    }
}

interface ShoppingCartItemCollectionProps {
    items: ShoppingCartItem[];
}

class ShoppingCartItemCollection extends ValueObject<ShoppingCartItemCollectionProps> implements ShoppingCartItemCollectionProps {
    items: ShoppingCartItem[];

    constructor(props: ShoppingCartItemCollectionProps) {
        super(props);
        this.items = props.items
    }
}

class ShoppingIdentifier extends Identifier<string> {
}

interface ShoppingCartProps {
    id: ShoppingIdentifier;
    itemsCollection: ShoppingCartItemCollection;
}

class ShoppingCart extends Entity<ShoppingCartProps> implements ShoppingCartProps {
    id: ShoppingIdentifier;
    itemsCollection: ShoppingCartItemCollection;

    constructor(props: ShoppingCartProps) {
        super(props);
        this.id = props.id;
        this.itemsCollection = props.itemsCollection;
    }
}

describe("ShoppingCart", () => {
    it("should have own property and props", () => {
        const shoppingCart = new ShoppingCart({
            id: new ShoppingCartItemIdentifier("shopping-cart"),
            itemsCollection: new ShoppingCartItemCollection({
                items: []
            })
        });
        assert.ok(Array.isArray(shoppingCart.itemsCollection.items));
        assert.strictEqual(shoppingCart.itemsCollection.items, shoppingCart.props.itemsCollection.props.items)
    });
});

@azu
Copy link
Member Author

azu commented May 20, 2018

Add docs

@azu
Copy link
Member Author

azu commented May 20, 2018

TypeScript support declaretion merging

interface UserData {
    name: string;
    birthday: Date;
    regdate: Date;
    social: string;
    passwordHash: string;
    isAdmin: boolean;
}
interface User extends UserData {}
class User {
    constructor(data: UserData) {
        Object.assign(this, data);
    }

    deservesCake() {
        return this.birthday.getDate() == new Date().getDate() || this.isAdmin;
    }
}

This approach has a limitation.
TS2564 Strict Property Initialization Checks does not work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant