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

New framework #7

Merged
merged 28 commits into from
Apr 15, 2016
Merged

New framework #7

merged 28 commits into from
Apr 15, 2016

Conversation

azu
Copy link
Owner

@azu azu commented Apr 11, 2016

新しいフレームワーク(1)

Presentation: http://azu.github.io/slide/2016/bikeshedjs/javascript-read-write-stack.html

Purpose

  • Scalable
  • Split up Read And Write stack

TODO

// TODO: inherit event emitter
export default class UseCase 
  • add test
  • add flow
  • add flow logger

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

ChangeLog

UseCase

Invoke pattern

-                context.execute(CompleteLoadingDocumentFactory.create(totalPageNumber));
+                context.useCase(CompleteLoadingDocumentFactory.create()).execute(totalPageNumber);

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

UseCase

UseCase do following

  1. get data
  2. modify data
  3. save data 🆕

A single file contain Factory and UseCase.

import documentRepository from "../infra/DocumentRepository";
import UseCase from "../framework/UseCase";
export class MarkClickedPageFactory {
    static create() {
        return new MarkClickedPageUseCase({
            documentRepository
        });
    }
}

export class MarkClickedPageUseCase extends UseCase {
    constructor({documentRepository}) {
        super();
        this.documentRepository = documentRepository;
    }

    execute(pageNumber) {
        const document = this.documentRepository.lastUsed();
        // mark page => (domain emit change)
        document.markAtPage(pageNumber);
        this.documentRepository.save(document);
    }
}

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

UseCase as Transaction

UseCase invoke UseCase.

import UseCase from "../framework/UseCase";
import TodoBackendServer from "../domain/TodoList/TodoBackendServer"
import todoListRepository, {TodoListRepository} from "../infra/TodoRepository"
import {AddTodoItemUseCase} from "./AddTodoItem";
import {UpdateTodoItemTitleUseCase} from "./UpdateTodoItemTitle";
import {RemoveTodoItemUseCase} from "./RemoveTodoItem";
export class TransactionTodoFactory {
    static create() {
        const todoBackendServer = new TodoBackendServer();
        return new UpdateTodoItemTitleUseCase({
            todoListRepository,
            todoBackendServer
        });
    }
}

export class TransactionTodoUseCase extends UseCase {
    /**
     * @param {TodoListRepository} todoListRepository
     * @param {TodoBackendServer} todoBackendServer
     */
    constructor({todoListRepository, todoBackendServer}) {
        super();
        this.todoListRepository = todoListRepository;
        this.todoBackendServer = todoBackendServer;
    }

    execute({title}) {
        const options = {
            todoListRepository: this.todoListRepository,
            todoBackendServer: this.todoBackendServer
        };
        const addTodo = new AddTodoItemUseCase(options);
        const updateTodoItem = new UpdateTodoItemTitleUseCase(options);
        const removeTodoItem = new RemoveTodoItemUseCase(options);
        const getLastItem = () => {
            const todoList = this.todoListRepository.lastUsed();
            return todoList.getAllTodoItems()[0];
        };
        // Add => Update => Remove
        return Promise.resolve().then(() => {
            return addTodo.execute({title});
        }).then(() => {
            const todoItem = getLastItem();
            return updateTodoItem.execute({itemId: todoItem.id, title: "UPDATING TITLE"});
        }).then(() => {
            const todoItem = getLastItem();
            return removeTodoItem.execute({itemId: todoItem.id});
        });
    }
}

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

Repository

As a result, testing is simple.

const assert = require("power-assert");
import {NewDocumentUseCase} from "../../src/js/UseCase/NewDocumentUseCase";
import Document from "../../src/js/domain/Document/Document";
import {DocumentRepository} from "../../src/js/infra/DocumentRepository";
describe("NewDocumentUseCase", function () {
    context("when execute", function () {
        it("should dispatch with document", function () {
            const pdfURL = "test.pdf";
            const documentRepository = new DocumentRepository();
            documentRepository.onChange(() => {
                const targetDocument = documentRepository.lastUsed();
                assert(targetDocument instanceof Document);
            });
            const useCase = new NewDocumentUseCase({documentRepository});
            return useCase.execute(pdfURL);
        });
    });
});

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

Newbie

UseCase -> State testing

eventDelegate will commnitcator between UseCase and State.

describe("ShowExportDialogUseCase", function () {
    context("when execute", function () {
        it("UseCase dispatch with output", function () {
            // mock event emitter
            const domainEventEmitter = new DomainEventEmitter();
            DomainEventAggregator.setEventEmitterForTesting(domainEventEmitter);
            const documentRepository = new DocumentRepository();
            const document = new Document();
            const expectedOutput = DocumentService.stringify(document);
            documentRepository.save(document);
            const useCase = new ShowExportDialogUseCase({documentRepository});
            useCase.onDispatch((key, output) => {
                assert(ShowExportDialogUseCase.name);
                assert.equal(expectedOutput, output);
            });
            return useCase.execute();
        });
        it("State receive dispatched output", function (done) {
            // Given
            const domainEventEmitter = new DomainEventEmitter();
            DomainEventAggregator.setEventEmitterForTesting(domainEventEmitter);
            const documentRepository = new DocumentRepository();
            const document = new Document();
            const expectedOutput = DocumentService.stringify(document);
            documentRepository.save(document);
            // when
            const store = new ExportStateStore({documentRepository});
            const useCase = new ShowExportDialogUseCase({documentRepository});
            eventDelegate(useCase, store);
            store.onChange(() => {
                const state = store.getState();
                assert.strictEqual(state.exporting.output, expectedOutput);
                done();
            });
            return useCase.execute();
        });
    });
});

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

UseCase

  • UseCase#dispatch
  • UseCase instance has dispatch function.
  • dispatch it and pass to state.
export default class ShowExportDialogUseCase extends UseCase{
    constructor({documentRepository}) {
        super();
        /**
         * @type {DocumentRepository}
         */
        this.documentRepository = documentRepository;
    }

    execute() {
        const document = this.documentRepository.lastUsed();
        const output = DocumentService.stringify(document);
        this.dispatch(ShowExportDialogUseCase.name, output);
    }
}

💡

Can we receive dispatch as constructor arguments?

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

Log Interface

const dispatcher = new Dispatcher();
// context connect dispatch with stores
const appContext = new AppContext({
    dispatcher,
    states: readAggregate.states
});
// LOG
const logMap = {};
dispatcher.onWillExecuteEachUseCase(useCase => {
    const startTimeStamp = performance.now();
    console.group(useCase.name, startTimeStamp);
    logMap[useCase.name] = startTimeStamp;
});
dispatcher.onDispatch((key, ...args) => {
    ContextLogger.logDispatch(key, ...args);
});
appContext.onChange(() => {
    ContextLogger.logOnChange(appContext.states);
});
dispatcher.onDidExecuteEachUseCase(useCase => {
    const startTimeStamp = logMap[useCase.name];
    const takenTime = performance.now() - startTimeStamp;
    console.info("Take time(ms): " + takenTime);
    console.groupEnd(useCase.name);
});

@azu
Copy link
Owner Author

azu commented Apr 11, 2016

Store

Add isChinging property toStore. it is needed for logger.

@azu
Copy link
Owner Author

azu commented Apr 12, 2016

StoreGroup

Introduce StoreGroup that collect store's changes.

vs. ReadAggregate ?

}

throwError(error) {
this.dispatch(`${this.useCaseName}:error`);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed onThrowError, but I think that listner should know from.

@azu
Copy link
Owner Author

azu commented Apr 15, 2016

Dispatcher

dispatcher has dispatch(payload) and onDispatch(handler).
These are used payload object that contain type value.

173d5e0

@azu azu merged commit 1cc89f2 into master Apr 15, 2016
@azu azu deleted the new-frameowrk branch April 15, 2016 10:46
@azu
Copy link
Owner Author

azu commented Apr 15, 2016

merged 🎉

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

Successfully merging this pull request may close these issues.

None yet

1 participant