From f9f4bbf19fbb53ce6c411958c51a25197cc3c4be Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Thu, 3 Jan 2019 03:33:42 +0100 Subject: [PATCH] feat: Add notifications --- dub.selections.json | 8 +-- source/d_webservice_example/application.d | 39 +++++++++++-- .../business/todo_notification_service.d | 43 +++++++++++++++ .../business/todo_service.d | 18 ++++-- .../component_registration.d | 15 ++++- .../controller/todo_controller.d | 27 +++++---- .../controller/todo_notification_controller.d | 55 +++++++++++++++++++ .../data/todo_notification.d | 10 ++++ .../dataaccess/in_memory_todo_repository.d | 5 ++ .../enums/todo_notification_type.d | 8 +++ .../d_webservice_example/facade/todo_facade.d | 51 +++++++++++++++++ .../d_webservice_example/mapper/todo_mapper.d | 2 +- .../mapper/todo_notification_mapper.d | 11 ++++ source/d_webservice_example/model/todo.d | 4 -- .../transport/todo_notification_cto.d | 10 ++++ 15 files changed, 274 insertions(+), 32 deletions(-) create mode 100644 source/d_webservice_example/business/todo_notification_service.d create mode 100644 source/d_webservice_example/controller/todo_notification_controller.d create mode 100644 source/d_webservice_example/data/todo_notification.d create mode 100644 source/d_webservice_example/enums/todo_notification_type.d create mode 100644 source/d_webservice_example/facade/todo_facade.d create mode 100644 source/d_webservice_example/mapper/todo_notification_mapper.d create mode 100644 source/d_webservice_example/transport/todo_notification_cto.d diff --git a/dub.selections.json b/dub.selections.json index 714698e..1b3d1a1 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -6,7 +6,7 @@ "botan-math": "1.0.3", "ddmp": "0.0.1-0.dev.3", "diet-ng": "1.5.0", - "eventcore": "0.8.39", + "eventcore": "0.8.40", "fluent-asserts": "0.12.3", "libasync": "0.8.3", "libdparse": "0.8.8", @@ -14,9 +14,9 @@ "memutils": "0.4.13", "mir-linux-kernel": "1.0.1", "openssl": "1.1.6+1.0.1g", - "stdx-allocator": "2.77.4", + "stdx-allocator": "2.77.5", "taggedalgebraic": "0.10.12", - "vibe-core": "1.4.5", + "vibe-core": "1.4.6", "vibe-d": "0.8.4" } -} \ No newline at end of file +} diff --git a/source/d_webservice_example/application.d b/source/d_webservice_example/application.d index 3e7961a..b6fa416 100644 --- a/source/d_webservice_example/application.d +++ b/source/d_webservice_example/application.d @@ -1,12 +1,15 @@ module d_webservice_example.application; -import aermicioi.aedi : locate, singleton; -import vibe.vibe; +import aermicioi.aedi : Container, locate, singleton; +import vibe.http.router : URLRouter; void main() @safe { import d_webservice_example.component_registration : registerComponents; - import d_webservice_example.controller.todo_controller : TodoController; + import vibe.core.core : runApplication; + import vibe.http.server : HTTPServerSettings, listenHTTP; + + setupLogging; auto container = singleton; scope (exit) @@ -15,12 +18,38 @@ void main() @safe container.registerComponents; container.instantiate; - auto router = new URLRouter; - router.registerRestInterface(container.locate!TodoController); auto settings = new HTTPServerSettings; settings.port = 8080; settings.bindAddresses = ["::", "0.0.0.0"]; + + auto router = setupRouter(container); listenHTTP(settings, router); runApplication; } + +void setupLogging() nothrow @safe +{ + import vibe.core.log : setLogFormat, FileLogger; + + debug + { + import vibe.core.log : LogLevel, setLogLevel; + + setLogLevel(LogLevel.diagnostic); + } + setLogFormat(FileLogger.Format.threadTime, FileLogger.Format.threadTime); +} + +URLRouter setupRouter(Container container) @safe +{ + import d_webservice_example.controller.todo_controller : TodoController; + import d_webservice_example.controller.todo_notification_controller : TodoNotificationController; + import vibe.web.rest : registerRestInterface; + import vibe.web.web : registerWebInterface; + + auto router = new URLRouter; + router.registerRestInterface(container.locate!TodoController); + router.registerWebInterface(container.locate!TodoNotificationController); + return router; +} diff --git a/source/d_webservice_example/business/todo_notification_service.d b/source/d_webservice_example/business/todo_notification_service.d new file mode 100644 index 0000000..548eb5a --- /dev/null +++ b/source/d_webservice_example/business/todo_notification_service.d @@ -0,0 +1,43 @@ +module d_webservice_example.business.todo_notification_service; + +class TodoNotificationService +{ + import d_webservice_example.data.todo_notification : TodoNotification; + import d_webservice_example.enums.todo_notification_type : TodoNotificationType; + import d_webservice_example.mapper.todo_mapper : asTodoTO; + import d_webservice_example.model.todo : Todo; + import std.traits : EnumMembers; + +private: + bool[void delegate(scope TodoNotification) @safe] listeners; + +public: + this() const nothrow pure @nogc @safe + { + } + + void registerListener(scope void delegate(scope const TodoNotification) @safe listener) @safe + { + listeners[listener] = true; + } + + void unRegisterListener(scope void delegate(scope const TodoNotification) @safe listener) @safe + { + listeners.remove(listener); + } + + static foreach (method; EnumMembers!TodoNotificationType) + { + import std.conv: to; + import std.format : format; + mixin(format!q{ + void onTodo%1$sd(const Todo todo) @safe + { + import std.algorithm.iteration : each; + + immutable todoChange = TodoNotification(TodoNotificationType.%1$s, todo); + listeners.byKey.each!(listener => listener(todoChange)); + } + }(method.to!string)); + } +} diff --git a/source/d_webservice_example/business/todo_service.d b/source/d_webservice_example/business/todo_service.d index 346ca43..9bb44cf 100644 --- a/source/d_webservice_example/business/todo_service.d +++ b/source/d_webservice_example/business/todo_service.d @@ -5,7 +5,9 @@ import std.uuid : UUID, randomUUID; class TodoService { + import d_webservice_example.business.todo_notification_service : TodoNotificationService; import d_webservice_example.data.todo_update_do : TodoUpdateDO; + import vibe.core.log : logInfo; private: TodoRepository todoRepository; @@ -17,10 +19,12 @@ public: this.todoRepository = todoRepository; } - Todo createTodo(Todo newTodo) @safe + Todo createTodo(const Todo newTodo) @safe { - immutable todo = Todo(newTodo.title, newTodo.content, randomUUID); - return todoRepository.save(todo); + immutable createdTodo = todoRepository.save(Todo(newTodo.title, + newTodo.content, randomUUID)); + logInfo("Created todo %s", createdTodo); + return createdTodo; } Todo[] getAllTodos() @safe @@ -46,13 +50,17 @@ public: if (todoUpdate.content != null) todo.content = todoUpdate.content; - return todoRepository.save(todo); + immutable updatedTodo = todoRepository.save(todo); + logInfo("Updated todo %s", updatedTodo); + return updatedTodo; } - void deleteTodo(UUID uuid) @safe + Todo deleteTodo(UUID uuid) @safe { immutable todo = getTodoByUuid(uuid); todoRepository.remove(todo); + logInfo("Deleted todo %s", todo); + return todo; } } diff --git a/source/d_webservice_example/component_registration.d b/source/d_webservice_example/component_registration.d index 44bf688..3b409d1 100644 --- a/source/d_webservice_example/component_registration.d +++ b/source/d_webservice_example/component_registration.d @@ -4,11 +4,20 @@ import aermicioi.aedi; void registerComponents(ConfigurableContainer container) @safe { + import d_webservice_example.business.todo_notification_service : TodoNotificationService; import d_webservice_example.business.todo_service : TodoRepository, TodoService; import d_webservice_example.controller.todo_controller : TodoController; + import d_webservice_example.controller.todo_notification_controller : TodoNotificationController; import d_webservice_example.dataaccess.in_memory_todo_repository : InMemoryTodoRepository; + import d_webservice_example.facade.todo_facade : TodoFacade; - container.configure.register!(TodoRepository, InMemoryTodoRepository); - container.configure.register!TodoService.autowire; - container.configure.register!TodoController.autowire; + with (container.configure) + { + register!TodoService.autowire; + register!TodoNotificationService.autowire; + register!TodoController.autowire; + register!TodoNotificationController.autowire; + register!(TodoRepository, InMemoryTodoRepository).autowire; + register!TodoFacade.autowire; + } } diff --git a/source/d_webservice_example/controller/todo_controller.d b/source/d_webservice_example/controller/todo_controller.d index ae984c7..0100a17 100644 --- a/source/d_webservice_example/controller/todo_controller.d +++ b/source/d_webservice_example/controller/todo_controller.d @@ -26,25 +26,26 @@ interface TodoApi class TodoController : TodoApi { - import d_webservice_example.business.todo_service : TodoService; + import d_webservice_example.facade.todo_facade : TodoFacade; import d_webservice_example.mapper.todo_mapper : asTodoTO; + import vibe.core.log : logDiagnostic; private: - TodoService todoService; + TodoFacade todoFacade; public: - this(TodoService todoService) nothrow pure @nogc @safe + this(TodoFacade todoFacade) nothrow pure @nogc @safe { - this.todoService = todoService; + this.todoFacade = todoFacade; } - // TODO: validation override TodoTO addTodo(string title, string content) @safe { import d_webservice_example.model.todo : Todo; - return todoService.createTodo(Todo(title, content)).asTodoTO; + logDiagnostic("Received request to add a todo with title '%s' and content '%s'", title, content); + return todoFacade.createTodo(Todo(title, content)).asTodoTO; } override TodoTO[] getTodos() @safe @@ -52,29 +53,35 @@ public: import std.algorithm.iteration : map; import std.array : array; - return todoService.getAllTodos.map!asTodoTO.array; + logDiagnostic("Received request to get all todos"); + return todoFacade.getAllTodos.map!asTodoTO.array; } override TodoTO getTodo(UUID _uuid) @safe { - return todoService.getTodoByUuid(_uuid).asTodoTO; + + logDiagnostic("Received request to get todo '%s'", _uuid); + return todoFacade.getTodoByUuid(_uuid).asTodoTO; } override TodoTO updateTodo(UUID _uuid, Nullable!string title, Nullable!string content) @safe { import d_webservice_example.data.todo_update_do : TodoUpdateDO; + logDiagnostic("Received request to update todo '%s' with title '%s' and content '%s'", + _uuid, title, content); TodoUpdateDO update; if (!title.isNull) update.title = title.get; if (!content.isNull) update.content = content.get; - return todoService.updateTodo(_uuid, update).asTodoTO; + return todoFacade.updateTodo(_uuid, update).asTodoTO; } override void deleteTodo(UUID _uuid) @safe { - todoService.deleteTodo(_uuid); + logDiagnostic("Received request to delete todo '%s'", _uuid); + todoFacade.deleteTodo(_uuid); } } diff --git a/source/d_webservice_example/controller/todo_notification_controller.d b/source/d_webservice_example/controller/todo_notification_controller.d new file mode 100644 index 0000000..3d307c6 --- /dev/null +++ b/source/d_webservice_example/controller/todo_notification_controller.d @@ -0,0 +1,55 @@ +module d_webservice_example.controller.todo_notification_controller; + +import vibe.http.websockets : WebSocket; +import vibe.web.web : path; + +@path("/api/v1/rt") +class TodoNotificationController +{ + import d_webservice_example.business.todo_notification_service : TodoNotificationService; + +private: + TodoNotificationService todoNotificationService; + +public: + this(TodoNotificationService todoNotificationService) nothrow pure @nogc @safe + { + this.todoNotificationService = todoNotificationService; + } + + @path("/todos") void getWebsocket(scope WebSocket socket) @safe + { + import d_webservice_example.data.todo_notification : TodoNotification; + import d_webservice_example.transport.todo_notification_cto : TodoNotificationCTO; + import vibe.core.log : logDiagnostic; + + logDiagnostic("Received request for todo notifications via web socket connection"); + + auto callback = (scope const TodoNotification todoNotification) { + import d_webservice_example.mapper.todo_notification_mapper : asTodoNotificationCTO; + import vibe.data.json : serializeToJsonString; + + logDiagnostic("Sending todo notification to client"); + socket.send(todoNotification.asTodoNotificationCTO.serializeToJsonString); + }; + todoNotificationService.registerListener(callback); + + dropWebsocketInput(socket); + + todoNotificationService.unRegisterListener(callback); + logDiagnostic("Client disconnected from web socket connection: %s(%s)", + socket.closeReason, socket.closeCode); + } +} + +private: + +void dropWebsocketInput(scope WebSocket socket) @safe +{ + while (socket.connected) + { + socket.waitForData; + if (socket.connected) + socket.receive((scope IncomingWebSocketMessage) {}); + } +} diff --git a/source/d_webservice_example/data/todo_notification.d b/source/d_webservice_example/data/todo_notification.d new file mode 100644 index 0000000..f3a897c --- /dev/null +++ b/source/d_webservice_example/data/todo_notification.d @@ -0,0 +1,10 @@ +module d_webservice_example.data.todo_notification; + +import d_webservice_example.enums.todo_notification_type : TodoNotificationType; +import d_webservice_example.model.todo : Todo; + +struct TodoNotification +{ + TodoNotificationType type; + Todo todo; +} diff --git a/source/d_webservice_example/dataaccess/in_memory_todo_repository.d b/source/d_webservice_example/dataaccess/in_memory_todo_repository.d index 3a4772c..8e81146 100644 --- a/source/d_webservice_example/dataaccess/in_memory_todo_repository.d +++ b/source/d_webservice_example/dataaccess/in_memory_todo_repository.d @@ -17,6 +17,11 @@ private: Todo[string] todos; public: + + this() nothrow pure @nogc @safe + { + } + override Todo save(Todo todo) { import std.range.primitives : empty; diff --git a/source/d_webservice_example/enums/todo_notification_type.d b/source/d_webservice_example/enums/todo_notification_type.d new file mode 100644 index 0000000..2d2fe7e --- /dev/null +++ b/source/d_webservice_example/enums/todo_notification_type.d @@ -0,0 +1,8 @@ +module d_webservice_example.enums.todo_notification_type; + +enum TodoNotificationType +{ + Create = "create", + Update = "update", + Delete = "delete" +} diff --git a/source/d_webservice_example/facade/todo_facade.d b/source/d_webservice_example/facade/todo_facade.d new file mode 100644 index 0000000..783bd48 --- /dev/null +++ b/source/d_webservice_example/facade/todo_facade.d @@ -0,0 +1,51 @@ +module d_webservice_example.facade.todo_facade; + +class TodoFacade +{ + import d_webservice_example.business.todo_notification_service : TodoNotificationService; + import d_webservice_example.business.todo_service : TodoService; + import d_webservice_example.data.todo_update_do : TodoUpdateDO; + import d_webservice_example.model.todo : Todo; + import std.uuid : UUID; + +private: + TodoNotificationService todoNotificationService; + TodoService todoService; + +public: + this(TodoNotificationService todoNotificationService, TodoService todoService) nothrow pure @nogc @safe + { + this.todoNotificationService = todoNotificationService; + this.todoService = todoService; + } + + Todo createTodo(Todo newTodo) @safe + { + immutable todo = todoService.createTodo(newTodo); + todoNotificationService.onTodoCreated(todo); + return todo; + } + + Todo[] getAllTodos() @safe + { + return todoService.getAllTodos; + } + + Todo getTodoByUuid(const UUID uuid) @safe + { + return todoService.getTodoByUuid(uuid); + } + + Todo updateTodo(UUID uuid, const TodoUpdateDO todoUpdate) @safe + { + immutable todo = todoService.updateTodo(uuid, todoUpdate); + todoNotificationService.onTodoUpdated(todo); + return todo; + } + + void deleteTodo(UUID uuid) @safe + { + immutable todo = todoService.deleteTodo(uuid); + todoNotificationService.onTodoDeleted(todo); + } +} diff --git a/source/d_webservice_example/mapper/todo_mapper.d b/source/d_webservice_example/mapper/todo_mapper.d index e761df5..5cd06ac 100644 --- a/source/d_webservice_example/mapper/todo_mapper.d +++ b/source/d_webservice_example/mapper/todo_mapper.d @@ -3,7 +3,7 @@ module d_webservice_example.mapper.todo_mapper; import d_webservice_example.model.todo : Todo; import d_webservice_example.transport.todo_to : TodoTO; -TodoTO asTodoTO(Todo todo) nothrow pure @safe @nogc +TodoTO asTodoTO(const Todo todo) nothrow pure @safe @nogc { return TodoTO(todo.uuid, todo.title, todo.content); } diff --git a/source/d_webservice_example/mapper/todo_notification_mapper.d b/source/d_webservice_example/mapper/todo_notification_mapper.d new file mode 100644 index 0000000..fb18913 --- /dev/null +++ b/source/d_webservice_example/mapper/todo_notification_mapper.d @@ -0,0 +1,11 @@ +module d_webservice_example.mapper.todo_notification_mapper; + +import d_webservice_example.data.todo_notification : TodoNotification; +import d_webservice_example.transport.todo_notification_cto : TodoNotificationCTO; + +TodoNotificationCTO asTodoNotificationCTO(const TodoNotification todoNotification) nothrow pure @safe @nogc +{ + import d_webservice_example.mapper.todo_mapper : asTodoTO; + + return TodoNotificationCTO(todoNotification.type, todoNotification.todo.asTodoTO); +} diff --git a/source/d_webservice_example/model/todo.d b/source/d_webservice_example/model/todo.d index e7e36d4..9543d75 100644 --- a/source/d_webservice_example/model/todo.d +++ b/source/d_webservice_example/model/todo.d @@ -3,7 +3,6 @@ module d_webservice_example.model.todo; struct Todo { import std.uuid : UUID; - import vibe.data.serialization : name; this(string title, string content) nothrow pure @safe @nogc { @@ -28,9 +27,6 @@ struct Todo string title; string content; - UUID uuid; - - @name("_id") string id; } diff --git a/source/d_webservice_example/transport/todo_notification_cto.d b/source/d_webservice_example/transport/todo_notification_cto.d new file mode 100644 index 0000000..de8c0b7 --- /dev/null +++ b/source/d_webservice_example/transport/todo_notification_cto.d @@ -0,0 +1,10 @@ +module d_webservice_example.transport.todo_notification_cto; + +import d_webservice_example.enums.todo_notification_type : TodoNotificationType; +import d_webservice_example.transport.todo_to : TodoTO; + +struct TodoNotificationCTO +{ + TodoNotificationType type; + TodoTO todo; +}