├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── Readme.md ├── horusVisualizerScript.js ├── package.json ├── src ├── Assets │ ├── Create-and-Get-Books.gif │ ├── Horus-Circular-Logo.png │ ├── HorusDemoCreate.gif │ ├── HorusDemoPart2.gif │ ├── Neo4j.png │ ├── New-Horus.png │ ├── create-and-get-customers.gif │ ├── sketchClientNew.png │ ├── sketchIntraservice.png │ ├── sketchWrapperNew.png │ └── slacksample.png ├── MockFrontEnd │ ├── App.jsx │ ├── component │ │ ├── Book.jsx │ │ └── CustomerProfile.jsx │ ├── containers │ │ ├── LeftContainer.jsx │ │ ├── MainContainer.jsx │ │ └── TopContainer.jsx │ ├── index.html │ ├── index.js │ ├── package.json │ ├── server │ │ ├── routes │ │ │ ├── booksRouter.js │ │ │ └── customersRouter.js │ │ └── server.js │ └── styles │ │ ├── _buttons.scss │ │ └── app.scss ├── books │ ├── booksClient.js │ ├── booksController.js │ ├── booksModel.js │ ├── booksServer.js │ └── package.json ├── customers │ ├── customersClient.js │ ├── customersController.js │ ├── customersModel.js │ ├── customersServer.js │ └── package.json ├── protos │ ├── books.proto │ └── customers.proto └── stubs │ ├── booksStub.js │ └── customersStub.js └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .env 4 | *.txt 5 | build 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |
5 |

HORUS

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 |

16 |
17 | A Distributed Tracing and Monitoring Tool for gRPC-Node Applications 18 |
19 |

20 |
21 | 22 | # What is Horus? 23 | 24 | Horus is a Distributed Tracing and Monitoring Tool for gRPC-Node Applications. 25 | 26 | 27 | Our team aims to provide the user a seamless experience in adding tracing and monitoring functionality to their application with our NPM Packages!

28 | 29 | 30 | Core Features :zap: 31 | - Horus traces, logs and monitors gRPC requests. Tracing and logging features include: 32 | 1. Provides data on which gRPC method was called for a specific gRPC request. 33 | 2. How long it took to complete the gRPC request. 34 | 3. Date/Time of Completion. 35 | 4. Traces any additional Intraservice Requests that were made in completing that request. 36 | 5. Request data will be outputted in a text file (but users can also visualize the data with our Neo4J Integration.) 37 | - Alerts user whenever gRPC request times go beyond a threshold of (+/-) 2 STD of its average request time using Slack Webhooks. 38 | - We provide a mock-microservice which performs gRPC requests AND intraservice gRPC requests between 2 different services. 39 | - Neo4j Integration to visualize data. 40 |
41 | 42 | The Mock-Microservice app. (in master branch) that we provide is a bookstore app. with the following services: 43 |
44 | - Books Service 45 | - Customers Service 46 |
47 | 48 | And yes, it does support intraservice gRPC requests! (Check out our setup tutorial in the table of contents below). 49 | 50 |
51 | 52 | *All modules can be found under the npm organization @horustracer.* 53 | *If you have any questions, please reach out to the team at: HorusTracerOfficial@gmail.com* 54 | 55 |
56 | 57 | ## Table of Contents 58 | 59 | * [Installation](#Installation) 60 | 61 | * [Branch Information](#Branch-Information) 62 | 63 | * [Step-by-Step Tutorial for Horus](#Step-By-Step-Tutorial-for-Horus) 64 | 65 | * [Monitoring Features and Setup](#Monitoring-Features-and-Setup) 66 | 67 | * [API Documentation](#Api-Documentation) 68 | 69 | * [Setting up Mock-Microservice App. with Horus (Which we provide)](#Setting-up-Mock-Microservice-With-Horus) 70 | 71 | * [Setting up Neo4j for Visualizing Request Data](#Setting-Up-Neo4j-for-Visualizing-Request-Data) 72 | 73 | * [Future Features](#Future-Features) 74 | 75 | * [Contributing](#Contributing) 76 | 77 | * [License](#License) 78 | 79 | * [Authors](#Authors) 80 | 81 |
82 |
83 | 84 | # Installation 85 | 86 | 87 | Installing Horus into your application is quick and easy. 88 |
89 | Follow the steps below to set up the ClientWrapper, ServerWrapper, and any intraservice requests that you have. 90 | 91 | *For a more in depth guide to installing Horus, check the tutorial right below the installation section.* 92 | 93 | NPM Installation: 94 | ```js 95 | npm install @horustracer/ClientWrapper //(Mandatory) 96 | npm install @horustracer/ServerWrapper //(Mandatory) 97 | npm install @horustracer/Visualizer //(optional, for Neo4J integration to visualize request data.) 98 | 99 | or 100 | 101 | npm install @horustracer/ClientWrapper @horustracer/ServerWrapper @horustracer/Visualizer 102 | ``` 103 |
104 | 105 | 1. Setting up the ClientWrapper 106 |
107 | 108 | - Import the ClientWrapper from @horustracer/ClientWrapper into your stub (gRPC client) file. 109 | - Initialize a new instance of the ClientWrapper, passing in the gRPC client, service and output text file name. 110 | - Then export the ClientWrapper in the place of your previous stub. 111 | 112 | ```js 113 | const HorusClientWrapper = require('@horustracer/ClientWrapper'); 114 | const grpc = require("grpc"); 115 | const protoLoader = require("@grpc/proto-loader"); 116 | const path = require('path'); 117 | const PROTO_PATH = path.join(__dirname, "../protos/books.proto"); 118 | 119 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 120 | keepCase: true, 121 | longs: String, 122 | enums: String, 123 | arrays: true 124 | }); 125 | 126 | const BooksService = grpc.loadPackageDefinition(packageDefinition).BooksService; 127 | 128 | const client = new BooksService ( 129 | "localhost:30043", 130 | grpc.credentials.createInsecure() 131 | ); 132 | 133 | //process.env.HORUS_DB utilizes environment variables. You can replace it with your MongoDB URI. 134 | //process.env.SLACK_URL utilizes environment variables. You can replace it with your SLACK URL. 135 | const ClientWrapper = new HorusClientWrapper(client, BooksService, 'books.txt', 'main', `${process.env.HORUS_DB}`, `${process.env.SLACK_URL}`); 136 | 137 | module.exports = ClientWrapper; 138 | ``` 139 |
140 | 141 | 2. Setting up the ServerWrapper 142 |
143 | 144 | - Import the ServerWrapper into your server file. 145 | - Rather than invoking the server.addService method, create a new instance of the ServerWrapper, passing in the server, proto, and methods as arguments. 146 | 147 | ```js 148 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 149 | const grpc = require('grpc'); 150 | const protoLoader = require("@grpc/proto-loader"); 151 | const path = require('path'); 152 | const controller = require("./booksController.js"); 153 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 154 | 155 | const ServerWrapper = new HorusServerWrapper(server, booksProto.BooksService.service, { 156 | CreateBook: async (call, callback) => { 157 | const book = call.request; 158 | 159 | let result = await controller.CreateBook(book); 160 | 161 | callback(null, result); 162 | }) 163 | 164 | server.bind("127.0.0.1:30043", grpc.ServerCredentials.createInsecure()); 165 | server.start(); 166 | ``` 167 |
168 | 169 | 3. Intraservice Requests 170 |
171 | 172 | - ***If you don't have any intraservice requests, you're all done and can skip this part.*** 173 | - If you do, invoke the makeHandShakeWithServer method in your intraservice request's callback. The makeHandShakeWithServer method lives on the ClientWrapper. It's first argument is the ServerWrapper it's being invoked in and the second is the name of the intraservice request. (Remember because of part 1, your stub should be exporting the ClientWrapper rather than the gRPC client); 174 | 175 | ```js 176 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 177 | const booksStub = require('../stubs/booksStubs.js) 178 | 179 | const CustomerServerWrapper = new HorusServerWrapper(server, customersProto.CustomersService.service, { 180 | GetCustomer: async (call, callback) => { 181 | const customerId = call.request; 182 | 183 | const customer = await controller.GetCustomer(customerId); 184 | 185 | const customersFavoriteBookId = {favBookId: customer.favBookId}; 186 | 187 | booksStub.GetBookById(customersFavoriteBookId, (error, response) => { 188 | 189 | booksStub.makeHandShakeWithServer(CustomerServerWrapper, 'GetBookById'); 190 | 191 | callback(null, result); 192 | }); 193 | }) 194 | ``` 195 | 196 | Once you have completed this step for your intraservice requests, you're all done. 197 | 198 |
199 | 200 | [↥Back to top](#Table-Of-Contents) 201 | 202 |
203 | 204 | 205 | # Branch Information 206 | 207 | - Master - Includes Mock-Microservice, supports Intraservice Request. 208 | - Staging - Staged version of master. Think of it like a draft of the master branch. . 209 | - Package Branch - Includes source code for our 3 NPM packages. 210 | 1. @horustracer/ClientWrapper 211 | 2. @horustracer/ServerWrapper 212 | 3. @horustracer/Visualizer 213 | 214 | 215 | To iterate fork to your own repository and submit PRs. 216 | 217 | Click [here](#Contributing) to see different ways of contributing. 218 | 219 | 220 |
221 | 222 | [↥Back to top](#Table-Of-Contents) 223 | 224 |
225 | 226 | 227 | # Step-by-Step Tutorial For Horus 228 | 229 | *Note: This does NOT include setting up the Mock Microservice Application. Check that section below, if you're interested in running the mock microservice application with the Horus Tool.* 230 |
231 | 232 | Installing Horus into your application is quick and easy. 233 | Follow the steps below to set up the ClientWrapper, ServerWrapper, and any intraservice handshake functions you may need. 234 | 235 |
236 | 237 | 1. Setting up the ClientWrapper 238 |
239 | 240 | The Horus ClientWrapper does its magic by "wrapping" your preexisting gRPC stub (gRPC client). Think of it as a middleman which lives between you (the developer) and your stub. Horus uses a similar approach for the ServerWrapper, except it wraps your server methods rather than the gRPC server itself. 241 | 242 |
243 |

244 | 245 |

246 |
247 | 248 | Install the ClientWrapper by running npm install @horustracer/ClientWrapper. Then "require" in the ClientWrapper into your stub (gRPC client) file. 249 | 250 | ```js 251 | 252 | // run "npm install @horustracer/ClientWrapper" in your terminal 253 | 254 | const HorusClientWrapper = require('@horustracer/ClientWrapper') 255 | 256 | ``` 257 |
258 | 259 | Now install and require in the grpc, @grpc.proto-loader, and path modules as you normally would. 260 | Using grpc.proto-loader, we are dynamically generated code from the proto files. This is opposed to static generation of code using the protoc compiler. 261 | 262 | We installed the 'path' module to help us get the path of where the .proto file is located in your file system. 263 | 264 |
265 | 266 | ```js 267 | 268 | // run "npm install grpc @grpc/proto-loader path" 269 | 270 | const HorusClientWrapper = require('@horustracer/ClientWrapper'); 271 | const grpc = require("grpc"); 272 | const protoLoader = require("@grpc/proto-loader"); 273 | const path = require('path'); 274 | const PROTO_PATH = path.join(__dirname, "../protos/books.proto"); 275 | 276 | ``` 277 |
278 | 279 | Next set up your package definition, service, and client as you normally would with gRPC. 280 | 281 |
282 | 283 | ```js 284 | 285 | const HorusClientWrapper = require('@horustracer/ClientWrapper'); 286 | const grpc = require("grpc"); 287 | const protoLoader = require("@grpc/proto-loader"); 288 | const path = require('path'); 289 | const PROTO_PATH = path.join(__dirname, "../protos/books.proto"); 290 | 291 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 292 | keepCase: true, 293 | longs: String, 294 | enums: String, 295 | arrays: true 296 | }); 297 | 298 | const BooksService = grpc.loadPackageDefinition(packageDefinition).BooksService; 299 | 300 | const client = new BooksService ( 301 | "localhost:30043", 302 | grpc.credentials.createInsecure() 303 | ); 304 | 305 | ``` 306 |
307 | 308 | Now create a new instance of the ClientWrapper, passing in the client, service, and name of the text file you want to log requests to. 309 | Export the new instance of your ClientWrapper, rather than the gRPC client object. 310 | 311 |
312 | 313 | ```js 314 | const HorusClientWrapper = require('@horustracer/ClientWrapper'); 315 | const grpc = require("grpc"); 316 | const protoLoader = require("@grpc/proto-loader"); 317 | const path = require('path'); 318 | const PROTO_PATH = path.join(__dirname, "../protos/books.proto"); 319 | 320 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 321 | keepCase: true, 322 | longs: String, 323 | enums: String, 324 | arrays: true 325 | }); 326 | 327 | const BooksService = grpc.loadPackageDefinition(packageDefinition).BooksService; 328 | 329 | const client = new BooksService ( 330 | "localhost:30043", 331 | grpc.credentials.createInsecure() 332 | ); 333 | 334 | //process.env.HORUS_DB utilizes environment variables. You can replace it with your MongoDB URI. 335 | //process.env.SLACK_URL utilizes environment variables. You can replace it with your SLACK URL. 336 | const ClientWrapper = new HorusClientWrapper(client, BooksService, 'books.txt', 'main', `${process.env.HORUS_DB}`, `${process.env.SLACK_URL}`); 337 | 338 | module.exports = ClientWrapper; 339 | 340 | ``` 341 |
342 | 343 | Now any file that imports your stub will actually be importing a wrapped version of that stub . This allows you to easily integrate Horus into your existing codebase. After you create your stub file, move on to part 2! 344 |
345 |
346 | 347 | 348 | 2. Setting up the ServerWrapper 349 |
350 | 351 | Install the ServerWrapper by running npm install @horustracer/ServerWrapper. Then "require" the ServerWrapper into your server file. 352 | 353 | ```js 354 | // run "npm install @horustracer/ServerWrapper" in your terminal 355 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 356 | ``` 357 |
358 | 359 | Now "require" in the grpc and @grpc/proto-loader, and path modules. Make sure to also include your proto file and any controllers you might need. 360 | 361 |
362 | 363 | ```js 364 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 365 | const grpc = require('grpc'); 366 | const protoLoader = require("@grpc/proto-loader"); 367 | const path = require('path'); 368 | const controller = require("./booksController.js"); 369 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 370 | ``` 371 |
372 | 373 | Next, create your package definition, proto, and new server instance. 374 | 375 |
376 | 377 | ```js 378 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 379 | const grpc = require('grpc'); 380 | const protoLoader = require("@grpc/proto-loader"); 381 | const path = require('path'); 382 | const controller = require("./booksController.js"); 383 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 384 | 385 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 386 | keepCase: true, 387 | longs: String, 388 | enums: String, 389 | arrays: true, 390 | }); 391 | 392 | const booksProto = grpc.loadPackageDefinition(packageDefinition); 393 | 394 | const server = new grpc.Server(); 395 | 396 | ``` 397 |
398 | 399 | In plain gRPC-Node.js applications, you define your server methods with the server.addService method like below. 400 | 401 |
402 | 403 | ```js 404 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 405 | const grpc = require('grpc'); 406 | const protoLoader = require("@grpc/proto-loader"); 407 | const path = require('path'); 408 | const controller = require("./booksController.js"); 409 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 410 | 411 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 412 | keepCase: true, 413 | longs: String, 414 | enums: String, 415 | arrays: true, 416 | }); 417 | 418 | const booksProto = grpc.loadPackageDefinition(packageDefinition); 419 | 420 | const server = new grpc.Server(); 421 | 422 | server.addService(booksProto.BooksService.service, { 423 | CreateBook: async (call, callback) => { 424 | const book = call.request; 425 | 426 | let result = await controller.CreateBook(book); 427 | 428 | callback(null, result); 429 | }) 430 | 431 | ``` 432 |
433 | 434 | In order to wrap your server methods, Horus instead uses the ServerWrapper object (see the example below). A new instance of the ServerWrapper is created, passing in the plain gRPC server object, service, and methods. 435 | 436 |
437 | 438 | ```js 439 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 440 | const grpc = require('grpc'); 441 | const protoLoader = require("@grpc/proto-loader"); 442 | const path = require('path'); 443 | const controller = require("./booksController.js"); 444 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 445 | 446 | const ServerWrapper = new HorusServerWrapper(server, booksProto.BooksService.service, { 447 | CreateBook: async (call, callback) => { 448 | const book = call.request; 449 | 450 | let result = await controller.CreateBook(book); 451 | 452 | callback(null, result); 453 | }) 454 | ``` 455 |
456 | 457 | After you create a new instance of the ServerWrapper, continue as you normally would with gRPC-Node.js by invoking the server bind and server start methods. 458 | 459 |
460 | 461 | ```js 462 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 463 | const grpc = require('grpc'); 464 | const protoLoader = require("@grpc/proto-loader"); 465 | const path = require('path'); 466 | const controller = require("./booksController.js"); 467 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 468 | 469 | const ServerWrapper = new HorusServerWrapper(server, booksProto.BooksService.service, { 470 | CreateBook: async (call, callback) => { 471 | const book = call.request; 472 | 473 | let result = await controller.CreateBook(book); 474 | 475 | callback(null, result); 476 | }) 477 | 478 | server.bind("127.0.0.1:30043", grpc.ServerCredentials.createInsecure()); 479 | server.start(); 480 | ``` 481 |
482 | 483 | Now that your server file is complete, you can move on to part three! 484 |
485 |
486 | 487 | 3. Intraservice Requests 488 |
489 | 490 | Horus supports applications built with monolithic or microservice architectures. One of its best features is its ability to track requests between two or more microservices (aka intraservice requests). 491 | 492 | *** If your application is monolithic or doesn't make any intraservice requests, then you're all done and can skip this part! *** 493 | 494 | This is an example of what an intraservice request might look like (down below). Here, the customers service receives a request from a client. To fulfill that request, it makes an intraservice request to the books service. Once that intraservice request returns, the customers server then sends its response to the original client. 495 | 496 |
497 | 498 | ```js 499 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 500 | const booksStub = require('../stubs/booksStubs.js) 501 | 502 | const ServerWrapper = new HorusServerWrapper(server, customersProto.CustomersService.service, { 503 | GetCustomer: async (call, callback) => { 504 | const customerId = call.request; 505 | 506 | const customer = await controller.GetCustomer(customerId); 507 | 508 | const customersFavoriteBookId = {favBookId: customer.favBookId}; 509 | 510 | booksStub.GetBookById(customersFavoriteBookId, (error, response) => { 511 | 512 | callback(null, result); 513 | }); 514 | }) 515 | ``` 516 |
517 | 518 | To trace an intraservice request like the one above, Horus needs you to invoke a "handshake" function. This function is called "makeHandShakeWithServer" and lives as a method on the ClientWrapper object. 519 | 520 | Invoke makeHandShakeWithServer in the callback that runs after your intraservice request completes. As arguments, pass in the ServerWrapper object and the name of the request. 521 | 522 |
523 | 524 | ```js 525 | const HorusServerWrapper = require('@horustracer/ServerWrapper); 526 | const booksStub = require('../stubs/booksStubs.js) 527 | 528 | const CustomerServerWrapper = new HorusServerWrapper(server, customersProto.CustomersService.service, { 529 | GetCustomer: async (call, callback) => { 530 | const customerId = call.request; 531 | 532 | const customer = await controller.GetCustomer(customerId); 533 | 534 | const customersFavoriteBookId = {favBookId: customer.favBookId}; 535 | 536 | booksStub.GetBookById(customersFavoriteBookId, (error, response) => { 537 | 538 | booksStub.makeHandShakeWithServer(CustomerServerWrapper, 'GetBookById'); 539 | 540 | callback(null, result); 541 | }); 542 | }) 543 | ``` 544 | 545 | In this scenario, the booksStub is secretly a Horus Client Wrapper (remember how we changed the modules.export statement in part 1!). Because of this, we invoke makeHandShakeWithServer on the booksStub. Becaues this intraservice request is being fired from the CustomerServerWrapper, we pass in that object from a few lines earlier. Lastly, we pass in "GetBookById" since that's the name of the method making the intraservice request. 546 | 547 | What's the point of this handshake function? This function is what allows the ClientWrapper and ServerWrapper objects to communicate. Whenever the ClientWrapper completes a request, it now knows to pass the relevent information on to the ServerWrapper. Here's a conceptual sketch of what the handshake function is acomplishing. 548 | 549 |

550 | 551 |

552 | 553 |
554 | 555 | Once you've added in handshake functions for your intraservice requests, you're good to go! 556 | 557 |
558 | 559 | [↥Back to top](#Table-Of-Contents) 560 | 561 |
562 | 563 | # API Documentation 564 | 565 | *At the moment, Horus does not support SQL Databases, and only supports NoSQL Databases (MongoDB specifically). 566 | 567 | ClientWrapper: The Horus Client Wrapper is a class. It's constructor takes in five parameters: 568 | 569 | - gRPC client (object) 570 | - gRPC package (object) 571 | - output file name (string) 572 | - service name (string) 573 | - mongodb URI (string) 574 | - slack url (string) 575 | 576 | The "output file name" parameter is the name of the file you want to output request data to.
577 | The ClientWrapper will create that file in your directory if it does not already exist.
578 | The "service name" and database (MongoDB) url parameters are tracing and visualizing your requests.
579 | "Service name" is the name of the service which is using the stub (i.e the customers service might use the books stub).
580 | Horus currently only supports NoSQL (mongoDB) databases. Pass the connection link to your database in the "mongodb url" parameter. 581 | The mongoDB URI is the Database for tracing request data. Do not include your service database. 582 | 583 |
584 | 585 | ServerWrapper: The Horus Server Wrapper is a class. It's constructor takes in three parameters: 586 | - gRPC server (object) 587 | - gRPC package (object) 588 | - methods (object) 589 | 590 | The "methods" object is an object containing the defintions of your server methods. If your gRPC service had a GetBooks function, then the definition for GetBooks should be included here. 591 | 592 |
593 | 594 | Visualizer Object: 595 | 596 |
597 | 598 | logAverages 599 | - mongodb url (string) 600 | 601 | Horus supports NoSQL databases (MongoDB). Pass the connection link to the database with your request data in the "mongodb" parameter. (This should be the same database that you provide to the ClientWrapper) 602 | 603 |
604 | 605 | mapAveragesToNeo4j 606 | - mongodb URI (string) - Not your service's DB, the DB for storing request data. 607 | - neo4j url (string) 608 | - username (string) 609 | - password (string) 610 | 611 | Horus supports NoSQL (specifically mongoDB) databases.
612 | Pass the connection link to the database with your request data in the "mongodb" parameter (this should be the same database that you provide to the ClientWrapper).
613 | Pass in the url of your local Neo4j database instance to the "neo4j url" parameter.
614 | Pass in the username and password for that local database instance to the "username" and "password" parameters. 615 | 616 |
617 | 618 | [↥Back to top](#Table-Of-Contents) 619 | 620 |
621 | 622 | # Monitoring Features and Setup 623 | 624 | 625 | *Get Alerted by [Slack](https://slack.com)* 626 | 627 | Leave out the uncertainty of whether your application is running normally or abnormally with the integration of receiving slack notifications. When the processing time of any of your routes results in (+/-) two standard deviations from the norm (rolling average), an automated slack message will be notified to your select channel or through direct message to your slack account. 628 | 629 | In addtion, all routes processed will have the time taken stored in a non-relational database (e.g. mongoDB) to have a running log of the historic times used in dynically updating the alerting threshold. 630 | 631 | Metrics Shown in Notification 632 | An alert will be sent out with these key metrics: 633 | - specific functionality of application that is causing the issue 634 | - time it took to fulfull the request 635 | - trailing average time for functionality to complete 636 | - standard deivation of the processing time for functionality to complete 637 | 638 | *Sample Message* 639 |

640 | 641 |

642 | 643 | 644 | Quick Setup 645 | 1) Setup [Slack Webhook Account](https://api.slack.com/messaging/webhooks) 646 | 2) Setup [Mongo Database](https://docs.mongodb.com/manual/tutorial/getting-started) 647 | 3) Create .env file in every service. In this .env file include your Slack webhook url and Mongo database urls (for each service). 648 | 649 | Sample .env file: 650 | ```env 651 | [INSERT_SERVICE_1_NAME]_DB='[Enter URI String]' 652 | [INSERT_SERVICE_2_NAME]_DB='[Enter URI String]' 653 | [INSERT_SERVICE_3_NAME]_DB='[Enter URI String]' 654 | ... 655 | [INSERT_SERVICE_'N'_NAME]_DB='[Enter URI String]' 656 | 657 | [INSERT_EXTRA_DB_TO_STORE_TRACING_DATA]_DB = '[ENTER URI STRING]' 658 | 659 | SLACK_URL='[Enter Slack Webhooks Link]' 660 | ``` 661 | 662 | Mock store Application Example .env file: 663 | 664 | ```env 665 | BOOKS_DB= '[Enter URI String]' 666 | CUSTOMERS_DB= '[Enter URI String]' 667 | EXTRA_DB_FOR_TRACING = '[Enter URI String]' 668 | SLACK_URL= '[Enter Slack Webhooks link]' 669 | ``` 670 | 671 | 672 |
673 | 674 | [↥Back to top](#Table-Of-Contents) 675 | 676 |
677 | 678 | # Setting up Mock-Microservice with Horus 679 | 680 | In this setup, you will need to set up 3 MongoDB Databases (1 for Books Service, 1 for Customers Service, 1 for storing tracing data). 681 | (Optional) If you want to integrate our monitoring feature, you will need to follow the steps in the [above](#Monitoring-Features-Setup). 682 | 683 | 684 | The Mock-Microservice app. is a bookstore app. with the following services: 685 | - Books Service 686 | - Customers Service 687 |
688 | 689 | After following each step you will be spinning up our frontend, interact with the frontend to make different gRPC requests (e.g. createBook, getBook, etc.), and see the trace information of those requests in a text file. (You can also set up our monitoring system with slack, as well as integrate Neo4j for visualizing gRPC request traces). 690 | 691 |
692 | 693 | **'CreateCustomer' and 'GetCustomer' Methods are run respectively (GetCustomer method performs Intraservice request in order to get the customer's favorite book)** 694 |

695 | 696 |

697 | 698 |
699 | 700 | **'CreateBook' and 'GetAllBooks' Methods are run respectively** 701 |

702 | 703 |

704 | 705 |
706 | 707 | Steps: 708 | - Create a new directory in your local system. 709 | - Fork and clone our repository in your newly created directory. 710 | - Git add remote of our repository, and pull the files from the Horus master branch from the upstream repository (main Horus repository). Check the commands below. 711 | 712 | ``` 713 | git remote add upstream https://github.com/oslabs-beta/Horus 714 | git pull upstream master 715 | ``` 716 | 717 | - By now you should have all the files in the Horus upstream master branch pulled down to your directory. 718 | - Add .env files to each service (check sample above) and in the root directory as well. 719 | ``` 720 | *Please don't forget to set up your Mongo Databases for each service (Books, Customers) + an extra database for storing tracing data. 721 | 722 | Also if you want to use the slack integration, follow the steps in the 'Monitoring Features' Section. 723 | ``` 724 | - Go into each service directory and type: 'npm install' to download all the node modules for each service. Also npm install in the root directory. This includes the mockFrontEnd, Customers, and Books services. 725 | - Go to the root directory and run 'npm run servers' to run all the servers of each service, as well as run the build for webpack. (You can check the "scripts" key in the package.json for all the different scripts that are available). This should concurrently run all your services' servers, and build the webpack. 726 | - Open up another terminal and run 'npm run start'. 727 | - Go to localhost:3000, and interact with the frontend! 728 | - Check out the customers.txt and books.txt file in the root directory for your response information :smile: ! Also your database should also have a history of all the response data as well. 729 | 730 | 731 |
732 | 733 | [↥Back to top](#Table-Of-Contents) 734 | 735 |
736 | 737 | # Setting up Neo4j for Visualizing Request Data 738 | 739 | Horus uses Neo4j, a native graph database, to visualize your requests. If you've never worked with Neo4j, downlaod the [Neo4j Desktop](https://neo4j.com/download/) before you continue. While developing support for Neo4j, we encountered a nasty port collision bug. If the Neo4j Desktop asks you fix the configuration of your ports when starting your database, run the "killall java" to terminate any conflicting processes. 740 | 741 | Once you've downloaded the Neo4j Desktop and started your database, download the @horustracer/visualzer package. 742 | 743 | ```js 744 | const HorusVisualizer = require('@horustracer/visualizer); 745 | ``` 746 | 747 | The visualizer package is an object with methods. To implemented Horus's Neo4j functionality, invoke the "mapAveragesToNeo4j" method, passing in the url of your mongodb, as well as the url, username, and password for your Neo4j database. 748 | 749 | ```js 750 | const HorusVisualizer = require('@horustracer/visualizer); 751 | HorusVisualizer.mapAveragesToNeo4j('', '', '', '') 752 | ``` 753 | 754 | When invoked, the "mapAveragesToNeo4j" queries your database of requests and computes the average responseTimes for every request. This means you can run the function either as method in its own program, or embed the function with the business logic of another program. Note, the url you pass into the "mapAveragesToNeo4j" function should be the same url you pass to the Client Wrapper. 755 | 756 | Here's an Image of how your data should be visualized 757 |

758 | 759 |

760 |
761 | 762 |
763 | 764 | [↥Back to top](#Table-Of-Contents) 765 | 766 |
767 | 768 | # Future Features 769 | 770 | - [ ] Support for Client Streaming, Server Streaming, and Bi-Directional Streaming. 771 | - [ ] Provide Support for SQL Databases. 772 | - [ ] Fixing Docker Networking Issues. 773 | - [ ] Creating a Dashboard for tracing data. 774 | - [ ] Providing more extensive monitoring features (Health Checking: CPU usage, RAM usage, etc.) 775 | - [ ] Creating Unit Testing, Mock-Testing, E2E Testing 776 | - [ ] CI/CD Workflow (with TravisCI or CircleCI) 777 | 778 |
779 | 780 | [↥Back to top](#Table-Of-Contents) 781 | 782 |
783 | 784 | 785 | # Contributing 786 | 787 | We would love for you to test this application out and submit any issues you encounter. Also, feel free to fork to your own repository and submit PRs. 788 | 789 | Here are some of the ways you can start contributing! 790 | - Bug Fixes. 791 | - Adding Features (Check Future Features above). 792 | - Submitting or resolving any GitHub Issues. 793 | - Help market our platform. 794 | 795 | Any way that can spread word or improve Horus will really go a long way for us! We don't bite :smile: ! 796 | 797 |
798 | 799 | [↥Back to top](#Table-Of-Contents) 800 | 801 |
802 | 803 | # License 804 | 805 | [MIT](https://github.com/oslabs-beta/Horus/blob/master/LICENSE) 806 | 807 |
808 | 809 | [↥Back to top](#Table-Of-Contents) 810 | 811 |
812 | 813 | # Authors 814 | 815 | - [Alexander Young](https://github.com/youngalexj00) 816 | - [Daria Bondarenko](https://github.com/Bondarinka) 817 | - [Jim Armbruster](https://github.com/JIMvsTHEWORLD) 818 | - [Raymond Zheng](https://github.com/Rchan0100) 819 | - [Victor To](https://github.com/vicNYC) 820 | 821 |
822 | 823 | [↥Back to top](#Table-Of-Contents) 824 | 825 |
826 | 827 |
828 |
829 | -------------------------------------------------------------------------------- /horusVisualizerScript.js: -------------------------------------------------------------------------------- 1 | const HorusVisualizer = require('@horustracer/visualizer'); 2 | 3 | require('dotenv').config(); 4 | 5 | HorusVisualizer.mapAveragesToNeo4jBrowser(process.env.HORUS_DB, 'bolt://localhost:11002', 'neo4j', 'password'); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "version": "1.0.0", 4 | "description": "Main", 5 | "main": "main.js", 6 | "scripts": { 7 | "build": "NODE_ENV=production webpack", 8 | "start": "NODE_ENV=production nodemon src/MockFrontEnd/server/server.js", 9 | "servers": "concurrently \"npm run booksServer\" \"npm run customersServer\" \"npm run build\"", 10 | "booksServer": "nodemon src/books/booksServer.js", 11 | "customersServer": "nodemon src/customers/customersServer.js", 12 | "test": "jest --verbose" 13 | }, 14 | "author": "Horus", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@grpc/proto-loader": "^0.5.5", 18 | "@horustracer/clientwrapper": "^1.0.3", 19 | "@horustracer/serverwrapper": "^1.0.3", 20 | "@horustracer/visualizer": "^1.0.3", 21 | "concurrently": "^5.2.0", 22 | "dotenv": "^8.2.0", 23 | "eslint": "^7.5.0", 24 | "eslint-config-airbnb-base": "^14.2.0", 25 | "eslint-plugin-import": "^2.22.0", 26 | "grpc": "^1.24.3" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.10.5", 30 | "@babel/preset-env": "^7.10.4", 31 | "@babel/preset-react": "^7.10.4", 32 | "babel-loader": "^8.1.0", 33 | "css-loader": "^3.6.0", 34 | "nodemon": "^2.0.4", 35 | "sass": "^1.26.10", 36 | "sass-loader": "^9.0.2", 37 | "style-loader": "^1.2.1", 38 | "webpack": "^4.44.0", 39 | "webpack-cli": "^3.3.12" 40 | }, 41 | "keywords": [] 42 | } 43 | -------------------------------------------------------------------------------- /src/Assets/Create-and-Get-Books.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/Create-and-Get-Books.gif -------------------------------------------------------------------------------- /src/Assets/Horus-Circular-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/Horus-Circular-Logo.png -------------------------------------------------------------------------------- /src/Assets/HorusDemoCreate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/HorusDemoCreate.gif -------------------------------------------------------------------------------- /src/Assets/HorusDemoPart2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/HorusDemoPart2.gif -------------------------------------------------------------------------------- /src/Assets/Neo4j.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/Neo4j.png -------------------------------------------------------------------------------- /src/Assets/New-Horus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/New-Horus.png -------------------------------------------------------------------------------- /src/Assets/create-and-get-customers.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/create-and-get-customers.gif -------------------------------------------------------------------------------- /src/Assets/sketchClientNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/sketchClientNew.png -------------------------------------------------------------------------------- /src/Assets/sketchIntraservice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/sketchIntraservice.png -------------------------------------------------------------------------------- /src/Assets/sketchWrapperNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/sketchWrapperNew.png -------------------------------------------------------------------------------- /src/Assets/slacksample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Horus/55f359474fccf6ea8c0c361c5e6a516cbed3667d/src/Assets/slacksample.png -------------------------------------------------------------------------------- /src/MockFrontEnd/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styles from './styles/app.scss' 4 | import MainContainer from './containers/MainContainer.jsx' 5 | import TopContainer from './containers/TopContainer.jsx' 6 | import LeftContainer from './containers/LeftContainer.jsx' 7 | 8 | class App extends React.Component{ 9 | constructor(props){ 10 | super(props); 11 | this.state = { 12 | data: '', 13 | profile: '', 14 | } 15 | this.deleteBook = this.deleteBook.bind(this) 16 | this.deleteCustomer = this.deleteCustomer.bind(this) 17 | this.handleGetBooks = this.handleGetBooks.bind(this) 18 | this.handleGetCustomer = this.handleGetCustomer.bind(this) 19 | } 20 | 21 | handleGetBooks(e){ 22 | e.preventDefault(); 23 | fetch('http://localhost:3000/books', { 24 | method: 'GET', 25 | }) 26 | .then(res => res.json()) 27 | .then(data => { 28 | this.setState({data: data.books, profile: ''}) 29 | }) 30 | } 31 | 32 | handleGetCustomer(e, custId){ 33 | e.preventDefault(); 34 | fetch(`http://localhost:3000/customers/${custId}`, { 35 | method: 'GET', 36 | }) 37 | .then(res => res.json()) 38 | .then(data => { 39 | this.setState({data: '', profile: data}) 40 | }) 41 | } 42 | 43 | deleteBook(e, bookId){ 44 | e.preventDefault() 45 | let newBookList = this.state.data.filter(book => book.bookId !== bookId) 46 | this.setState({data: newBookList}) 47 | fetch(`http://localhost:3000/books/${bookId}`, { 48 | method: 'Delete' 49 | }) 50 | } 51 | 52 | deleteCustomer(e, custId){ 53 | e.preventDefault() 54 | this.setState({profile: ''}) 55 | fetch(`http://localhost:3000/customers/${custId}`, { 56 | method: 'Delete' 57 | }) 58 | } 59 | 60 | render(){ 61 | return( 62 |
63 | 64 |
65 | 66 | 67 |
68 |
69 | ) 70 | } 71 | } 72 | 73 | ReactDOM.render(, document.getElementById('root')) 74 | 75 | export default App -------------------------------------------------------------------------------- /src/MockFrontEnd/component/Book.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/app.scss' 3 | 4 | class Book extends React.Component { 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | bookId: '', 9 | key: '' 10 | } 11 | this.handleDeleteBook = this.handleDeleteBook.bind(this) 12 | } 13 | 14 | handleDeleteBook(e){ 15 | this.props.handleDeleteBook(e, this.props.bookId) 16 | } 17 | 18 | render () { 19 | return
20 |

{this.props.title}

21 |
    22 |
  • By: {this.props.author}
  • 23 |
  • {this.props.numberOfPages} pages
  • 24 |
  • Published By: {this.props.publisher}
  • 25 |
  • Book ID#: {this.props.bookId}
  • 26 |
27 | 28 |
29 | } 30 | } 31 | 32 | export default Book -------------------------------------------------------------------------------- /src/MockFrontEnd/component/CustomerProfile.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/app.scss' 3 | 4 | class CustomerProfile extends React.Component { 5 | 6 | constructor(props){ 7 | super(props); 8 | 9 | this.handleDeleteCustomer = this.handleDeleteCustomer.bind(this) 10 | } 11 | 12 | handleDeleteCustomer(e){ 13 | this.props.handleDeleteCustomer(e, this.props.custId) 14 | } 15 | 16 | render () { 17 | return
18 |

{this.props.name}

19 |
    20 |
  • Cust ID#: {this.props.custId}
  • 21 |
  • Age: {this.props.age}
  • 22 |
  • Address: {this.props.address}
  • 23 |
24 |

Favorite Book: {this.props.favBook.title}

25 |
    26 |
  • By: {this.props.favBook.author}
  • 27 |
  • {this.props.favBook.numberOfPages} pages
  • 28 |
  • Published By: {this.props.favBook.publisher}
  • 29 |
  • Book ID#: {this.props.favBook.bookId}
  • 30 |
31 | 32 |
33 | } 34 | } 35 | 36 | export default CustomerProfile -------------------------------------------------------------------------------- /src/MockFrontEnd/containers/LeftContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styles from '../styles/app.scss' 4 | 5 | class LeftContainer extends React.Component { 6 | constructor() { 7 | super(); 8 | this.state ={ 9 | custId: '', 10 | name: '', 11 | age: '', 12 | address: '', 13 | favBookId: '', 14 | getCustomerField: '' 15 | }; 16 | this.handleChange = this.handleChange.bind(this); 17 | this.handleCustomerSubmit = this.handleCustomerSubmit.bind(this); 18 | this.handleBookSubmit = this.handleBookSubmit.bind(this); 19 | this.handleReset = this.handleReset.bind(this); 20 | this.handleGetCustomer = this.handleGetCustomer.bind(this) 21 | } 22 | 23 | handleGetCustomer(e){ 24 | this.props.handleGetCustomer(e, this.state.getCustomerField) 25 | this.setState({getCustomerField: ''}) 26 | } 27 | 28 | handleChange(e) { 29 | this.setState({ [e.target.name]: e.target.value }); 30 | } 31 | 32 | handleReset(e) { 33 | this.setState({[e.target.name]: ''}) 34 | } 35 | 36 | handleCustomerSubmit(e) { 37 | e.preventDefault(); 38 | let url = 'http://localhost:3000/customers'; 39 | fetch(url, { 40 | method: 'POST', 41 | headers: {'Content-Type': 'application/json'}, 42 | body: JSON.stringify({ 43 | custId: this.state.custId, 44 | name: this.state.name, 45 | age: this.state.age, 46 | address: this.state.address, 47 | favBookId: this.state.favBookId 48 | }) 49 | }) 50 | .then(res => res.json()) 51 | //reset state here so that input fields are blank again after submitting. 52 | this.setState({ 53 | custId: '', 54 | name: '', 55 | age: '', 56 | address: '', 57 | favBookId: '' 58 | }) 59 | } 60 | 61 | handleBookSubmit(e) { 62 | e.preventDefault(); 63 | fetch('http://localhost:3000/books', { 64 | method: 'POST', 65 | headers: {'Content-Type': 'application/json'}, 66 | body: JSON.stringify({ 67 | title: this.state.title, 68 | author: this.state.author, 69 | numberOfPages: this.state.numberOfPages, 70 | publisher: this.state.publisher, 71 | bookId: this.state.bookId 72 | }) 73 | }) 74 | .then(res => res.json()) 75 | // Reset state here so the input fields are blank again after submitting. 76 | this.setState( 77 | {title: '', 78 | author: '', 79 | numberOfPages: '', 80 | publisher: '', 81 | bookId: '' 82 | }) 83 | } 84 | 85 | render(){ 86 | return( 87 |
88 |
89 |

Create New Customer

90 | 91 | 99 |
100 | 101 | 109 |
110 | 111 | 119 |
120 | 121 | 129 |
130 | 131 | 139 |
140 | 141 |
142 |
143 | 144 | 152 |
153 | 154 |
155 | 156 |
157 |

Add Book to Bookstore

158 | 159 | 168 |
169 | 170 | 178 |
179 | 180 | 188 |
189 | 190 | 198 |
199 | 200 | 208 |
209 | 210 |
211 | 212 |
213 | ) 214 | } 215 | } 216 | 217 | export default LeftContainer -------------------------------------------------------------------------------- /src/MockFrontEnd/containers/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styles from '../styles/app.scss' 4 | import Book from '../component/Book.jsx' 5 | import CustomerProfile from '../component/CustomerProfile.jsx' 6 | 7 | class MainContainer extends React.Component { 8 | 9 | constructor(props){ 10 | super(props); 11 | this.state = { 12 | items: '', 13 | profile: '' 14 | } 15 | this.handleDeleteBook = this.handleDeleteBook.bind(this) 16 | this.handleDeleteCustomer = this.handleDeleteCustomer.bind(this) 17 | } 18 | 19 | handleDeleteBook(e, bookId){ 20 | this.props.deleteBook(e, bookId) 21 | } 22 | 23 | handleDeleteCustomer(e, custId){ 24 | this.props.deleteCustomer(e, custId) 25 | this.setState({profile: ''}) 26 | } 27 | 28 | //this function will allow us to wait for props to be sent down and set state before the components render to the screen. That way when Get Customer or Get Books button is clicked, we can populate the relevent data. 29 | componentWillReceiveProps(nextProps) { 30 | if(nextProps.data){ 31 | this.setState({profile: ''}) 32 | this.setState({items: nextProps.data}) 33 | } 34 | if(nextProps.profile){ 35 | this.setState({items: ''}) 36 | this.setState({profile: nextProps.profile}) 37 | } 38 | } 39 | 40 | render(){ 41 | const bookList = this.state.items 42 | const profile = this.state.profile 43 | const items = [] 44 | if (bookList){ 45 | for (let i = bookList.length-1; i >=0 ; i--){ 46 | items.push( 47 | 48 | ) 49 | } 50 | } else if (profile){ 51 | items.push( 52 | 53 | ) 54 | } 55 | return( 56 |
57 | {items} 58 |
59 | ) 60 | } 61 | } 62 | 63 | export default MainContainer -------------------------------------------------------------------------------- /src/MockFrontEnd/containers/TopContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styles from '../styles/app.scss' 4 | 5 | class TopContainer extends React.Component { 6 | render(){ 7 | return( 8 |
9 | WELCOME TO NILE BOOK STORE! 10 |
11 | ) 12 | } 13 | } 14 | 15 | export default TopContainer -------------------------------------------------------------------------------- /src/MockFrontEnd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mock Book Store 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/MockFrontEnd/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './App.jsx'; 4 | 5 | render( 6 | , 7 | document.getElementById('root') 8 | ); -------------------------------------------------------------------------------- /src/MockFrontEnd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customers", 3 | "version": "1.0.0", 4 | "description": "npm file for customers microservice", 5 | "main": "index.js", 6 | "scripts": { 7 | "webpack": "webpack", 8 | "start": "nodemon server/server.js", 9 | "build": "NODE_ENV=production webpack", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Horus", 13 | "license": "ISC", 14 | "dependencies": { 15 | "express": "^4.17.1", 16 | "nodemon": "^2.0.4", 17 | "react": "^16.13.1", 18 | "react-dom": "^16.13.1" 19 | }, 20 | "devDependencies": { 21 | "webpack": "^4.44.0" 22 | }, 23 | "keywords": [] 24 | } 25 | -------------------------------------------------------------------------------- /src/MockFrontEnd/server/routes/booksRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const booksClient = require('../../../books/booksClient.js'); 3 | 4 | const booksRouter = express.Router(); 5 | 6 | booksRouter.post('/', booksClient.createBook, (req, res) => { 7 | return res.status(200).json(res.locals.book) 8 | }); 9 | 10 | booksRouter.get('/', booksClient.getBooks, (req, res) => { 11 | return res.status(200).json(res.locals.books) 12 | }); 13 | 14 | booksRouter.delete('/:bookId', booksClient.deleteBook, (req, res) => { 15 | return res.status(200); 16 | }); 17 | 18 | module.exports = booksRouter; 19 | -------------------------------------------------------------------------------- /src/MockFrontEnd/server/routes/customersRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const customersClient = require('../../../customers/customersClient.js'); 3 | 4 | const customersRouter = express.Router(); 5 | 6 | customersRouter.post('/', customersClient.createCustomer, (req, res) => { 7 | return res.status(200).json(res.locals.customers) 8 | }); 9 | 10 | customersRouter.get('/:custId', customersClient.getCustomer, (req, res) => { 11 | return res.status(200).json(res.locals.customers) 12 | }); 13 | 14 | customersRouter.delete('/:custId', customersClient.deleteCustomer, (req, res) => { 15 | return res.status(200); 16 | }); 17 | 18 | module.exports = customersRouter; 19 | -------------------------------------------------------------------------------- /src/MockFrontEnd/server/server.js: -------------------------------------------------------------------------------- 1 | /* Because HTTP2 is not compatible with browsers, an express proxy server is required to translate the gRPC requests and responses. */ 2 | 3 | const path = require('path'); 4 | const express = require('express'); 5 | 6 | const app = express(); 7 | const customersRouter = require('./routes/customersRouter.js'); 8 | const booksRouter = require('./routes/booksRouter.js'); 9 | 10 | app.use(express.json()); 11 | 12 | app.use('/customers', customersRouter); 13 | 14 | app.use('/books', booksRouter); 15 | 16 | app.use('/build', express.static(path.join(__dirname, '../../build'))); 17 | 18 | app.get('*', (req, res) => { 19 | res.sendFile(path.join(__dirname, '../index.html')); 20 | }); 21 | 22 | app.use((req, res) => { 23 | console.log('Unknown route. Try another route.'); 24 | return res.status(404); 25 | }); 26 | 27 | app.listen(3000, () => console.log('Listening on port 3000')); 28 | -------------------------------------------------------------------------------- /src/MockFrontEnd/styles/_buttons.scss: -------------------------------------------------------------------------------- 1 | button { 2 | font-family: sans-serif; 3 | border: none; 4 | border-radius: 5px; 5 | padding: 5px 5px 5px 5px; 6 | cursor: pointer; 7 | outline: none; 8 | } 9 | 10 | button:active { 11 | transform: translateY(3px) 12 | } 13 | 14 | #getCustomerbutton { 15 | background: #1aa260; 16 | color: white; 17 | } 18 | 19 | #getCustomerbutton:hover{ 20 | background-color: #289460; 21 | } 22 | 23 | #createCustomerButton { 24 | background: #4c8bf5; 25 | color: white; 26 | } 27 | 28 | #createCustomerButton:hover { 29 | background: #5d8fe4; 30 | } 31 | 32 | #deleteCustomerButton { 33 | background: #db4a39; 34 | color: white; 35 | } 36 | 37 | #deleteCustomerButton:hover { 38 | background: #cb5749; 39 | } 40 | 41 | #getBooksbutton { 42 | background: #1aa260; 43 | color: white; 44 | } 45 | 46 | #getBooksbutton:hover { 47 | background-color: #289460; 48 | } 49 | 50 | #createBookButton { 51 | background: #4c8bf5; 52 | color: white; 53 | } 54 | 55 | #createBookButton:hover { 56 | background: #5d8fe4; 57 | } 58 | 59 | #deleteBooksbutton { 60 | background: #db4a39; 61 | color: white; 62 | } 63 | 64 | #deleteBooksbutton:hover { 65 | background: #cb5749; 66 | } 67 | -------------------------------------------------------------------------------- /src/MockFrontEnd/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import 'buttons.scss'; 2 | 3 | body { 4 | margin: 0; 5 | } 6 | 7 | .MainBody { 8 | display: flex; 9 | height: 100%; 10 | } 11 | 12 | .MainContainer { 13 | background: rgb(144, 221, 238); 14 | width: 67%; 15 | overflow: scroll; 16 | } 17 | 18 | .TopContainer { 19 | border-bottom: solid 1px black; 20 | background: navy; 21 | height: 100px; 22 | width: 100%; 23 | color: white; 24 | } 25 | 26 | .LeftContainer { 27 | background: rgb(224, 110, 110); 28 | border-right: solid 1px; 29 | height: 100%; 30 | width: 33%; 31 | .createCustomerForm { 32 | background: rgb(224, 110, 110); 33 | } 34 | .CreateBookForm { 35 | background: rgb(224, 110, 110); 36 | } 37 | } 38 | 39 | .bookBox { 40 | height: 200px; 41 | width: 300px; 42 | border: solid black; 43 | } 44 | 45 | .custProfile { 46 | border: solid black; 47 | height: 350px; 48 | width: 300px; 49 | 50 | } -------------------------------------------------------------------------------- /src/books/booksClient.js: -------------------------------------------------------------------------------- 1 | const bookStub = require('../stubs/booksStub.js'); 2 | 3 | const booksClient = {}; 4 | 5 | booksClient.createBook = (req, res, next) => { 6 | const book = { 7 | title: req.body.title, 8 | author: req.body.author, 9 | numberOfPages: req.body.numberOfPages, 10 | publisher: req.body.publisher, 11 | bookId: req.body.bookId, 12 | }; 13 | const callback = (error, data) => { 14 | res.locals.book = data; 15 | if (error) console.log('sorry, there was an error', error); 16 | return next(); 17 | }; 18 | bookStub.CreateBook(book, callback); 19 | }; 20 | 21 | booksClient.getBooks = (req, res, next) => { 22 | const callback = (error, data) => { 23 | res.locals.books = data; 24 | if (error) console.log('sorry, there was an error', error); 25 | return next(); 26 | }; 27 | bookStub.GetBooks({}, callback); 28 | }; 29 | 30 | booksClient.deleteBook = (req, res, next) => { 31 | bookId = { bookId: req.params.bookId }; 32 | const callback = (error, data) => { 33 | if (error) console.log('sorry, there was an error', error); 34 | return next(); 35 | }; 36 | bookStub.DeleteBook(bookId, callback); 37 | }; 38 | 39 | module.exports = booksClient; 40 | -------------------------------------------------------------------------------- /src/books/booksController.js: -------------------------------------------------------------------------------- 1 | const booksModel = require('./booksModel.js'); 2 | 3 | const booksController = {}; 4 | 5 | booksController.createBook = async (book) => { 6 | return await booksModel 7 | .create(book) 8 | .then((response) => response) 9 | .catch((error) => { 10 | console.log('ERROR from createBook controller : ', error); 11 | return "error"; 12 | }); 13 | }; 14 | 15 | booksController.deleteBook = async (bookId) => { 16 | return await booksModel 17 | .findOneAndDelete(bookId) 18 | .then((response) => response) 19 | .catch((error) => { 20 | console.log('ERROR from deleteBook controller : ', error); 21 | return 'error'; 22 | }); 23 | }; 24 | 25 | booksController.getBookByID = async (bookId, callback) => { 26 | return await booksModel 27 | .findOne(bookId) 28 | .then((response) => response) 29 | .catch((error) => { 30 | console.log('ERROR from getBookbyID controller : ', error); 31 | return 'error'; 32 | }); 33 | }; 34 | 35 | booksController.getBooks = async () => { 36 | return await booksModel 37 | .find({}) 38 | .then((response) => { 39 | let arr = []; 40 | for (let i = 0; i < response.length; i++) { 41 | arr.push({ 42 | title: response[i].title, 43 | author: response[i].author, 44 | numberOfPages: response[i].numberOfPages, 45 | publisher: response[i].publisher, 46 | bookId: response[i].bookId, 47 | }); 48 | } 49 | return arr; 50 | }) 51 | .catch((error) => { 52 | console.log('ERROR from the getBooks controller : ', error); 53 | }); 54 | }; 55 | 56 | module.exports = booksController; 57 | -------------------------------------------------------------------------------- /src/books/booksModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | require('dotenv').config(); 3 | 4 | // pull schema from the mongoose object 5 | const { Schema } = mongoose; 6 | 7 | // DB link for books data. 8 | const book_db_URI = `${process.env.BOOKS_DB}`; 9 | 10 | // connect the database, if error, log will be sent to the terminal 11 | mongoose 12 | .connect(book_db_URI, { useNewUrlParser: true, useUnifiedTopology: true }) 13 | .then(() => console.log('Connected!!!********* Books Database is live!!!')) 14 | .catch((err) => console.log('Connection Error ', err)); 15 | 16 | // Schema for the database 17 | const BooksSchema = new Schema({ 18 | bookId: { 19 | type: Number, 20 | required: true, 21 | }, 22 | 23 | title: { 24 | type: String, 25 | required: true, 26 | }, 27 | author: { 28 | type: String, 29 | required: true, 30 | }, 31 | numberOfPages: { 32 | type: Number, 33 | required: false, 34 | }, 35 | publisher: { 36 | type: String, 37 | required: false, 38 | }, 39 | }); 40 | 41 | // Database Model creation to be exported 42 | const booksModel = mongoose.model('booksModel', BooksSchema); 43 | 44 | module.exports = booksModel; 45 | -------------------------------------------------------------------------------- /src/books/booksServer.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const grpc = require('grpc'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const HorusServerWrapper = require('@horustracer/serverwrapper'); 5 | const controller = require('./booksController.js'); 6 | 7 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 8 | 9 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 10 | keepCase: true, 11 | longs: String, 12 | enums: String, 13 | arrays: true, 14 | }); 15 | 16 | const booksProto = grpc.loadPackageDefinition(packageDefinition); 17 | 18 | const server = new grpc.Server(); 19 | 20 | // The Horus Server Wrapper "wraps" each server method passed in 21 | // Replace server.addService({ .. methods}) with Const ServerWrapper = new HorusServerWrapper (serverObject, service, {..methods}) 22 | // Your preexisting methods can remain entirely the same 23 | const ServerWrapper = new HorusServerWrapper( 24 | server, 25 | booksProto.BooksService.service, 26 | { 27 | CreateBook: async (call, callback) => { 28 | const result = await controller.createBook(call.request); 29 | 30 | if (result === 'error') { 31 | return callback({ 32 | code: grpc.status.STATUS_UNKNOWN, 33 | message: 'There was an error writing to the database', 34 | }); 35 | } 36 | 37 | callback(null, { 38 | title: result.title, 39 | author: result.author, 40 | numberOfPages: result.numberOfPages, 41 | publisher: result.publisher, 42 | bookId: result.bookId, 43 | }); 44 | }, 45 | DeleteBook: async (call, callback) => { 46 | const result = await controller.deleteBook(call.request); 47 | 48 | if (result === 'error') { 49 | return callback({ 50 | code: grpc.status.STATUS_UNKNOWN, 51 | message: 'There was an error deleting from the database', 52 | }); 53 | } 54 | 55 | callback(null, {}); 56 | }, 57 | GetBooks: async (call, callback) => { 58 | const result = await controller.getBooks(); 59 | 60 | callback(null, { books: result }); 61 | }, 62 | GetBookByID: async (call, callback) => { 63 | const result = await controller.getBookByID(call.request, callback); 64 | if (result === 'error') { 65 | return callback({ 66 | code: grpc.status.STATUS_UNKNOWN, 67 | message: 'There was an error reading from the database', 68 | }); 69 | } 70 | callback(null, { 71 | title: result.title, 72 | author: result.author, 73 | numberOfPages: result.numberOfPages, 74 | publisher: result.publisher, 75 | bookId: result.bookId, 76 | }); 77 | }, 78 | }, 79 | ); 80 | 81 | server.bind('127.0.0.1:30043', grpc.ServerCredentials.createInsecure()); 82 | console.log('booksServer.js running at http://127.0.0.1:30043'); 83 | 84 | console.log('call from books server'); 85 | 86 | server.start(); 87 | -------------------------------------------------------------------------------- /src/books/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "books", 3 | "version": "1.0.0", 4 | "description": "npm file for books microservice", 5 | "main": "booksServer.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Horus", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@grpc/proto-loader": "^0.5.5", 13 | "@horustracer/serverwrapper": "^1.0.3", 14 | "dotenv": "^8.2.0", 15 | "grpc": "^1.24.3", 16 | "mongoose": "^5.8.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/customers/customersClient.js: -------------------------------------------------------------------------------- 1 | //client.js files are a way for us to use these functions without using the browser on the front end. 2 | const customersClient = {}; 3 | const { resolve } = require('path'); 4 | 5 | const customerStub = require('../stubs/customersStub.js'); 6 | 7 | customersClient.createCustomer = (req, res, next) => { 8 | const customer = { 9 | custId: req.body.custId, 10 | name: req.body.name, 11 | age: req.body.age, 12 | address: req.body.address, 13 | favBookId: req.body.favBookId 14 | }; 15 | 16 | const callback = (error, data) => { 17 | res.locals.customers = data; 18 | if (error) console.log('sorry, there was an error', error); 19 | return next(); 20 | }; 21 | customerStub.CreateCustomer(customer, callback); 22 | }; 23 | 24 | customersClient.getCustomer = (req, res, next) => { 25 | custId = { custId: req.params.custId }; 26 | const callback = (error, data) => { 27 | res.locals.customers = data; 28 | if (error) console.log('sorry, there was in error: ', error); 29 | return next(); 30 | }; 31 | customerStub.GetCustomer(custId, callback); 32 | }; 33 | 34 | customersClient.deleteCustomer = (req, res, next) => { 35 | custId = { custId: req.params.custId }; 36 | const callback = (error, data) => { 37 | if (error) console.log('sorry, there was an error', error); 38 | return next(); 39 | }; 40 | customerStub.DeleteCustomer(custId, callback); 41 | }; 42 | 43 | module.exports = customersClient; 44 | -------------------------------------------------------------------------------- /src/customers/customersController.js: -------------------------------------------------------------------------------- 1 | const customersModel = require('./customersModel.js'); 2 | 3 | const customersController = {}; 4 | const path = require('path'); 5 | 6 | customersController.createCustomer = async (customer) => { 7 | return await customersModel.create(customer) 8 | .then((response) => response) 9 | .catch((error) => { 10 | console.log('ERROR from createCustomer controller : ', error); 11 | return 'error'; 12 | }) 13 | }; 14 | 15 | customersController.deleteCustomer = async (custId) => { 16 | return await customersModel.findOneAndDelete(custId) 17 | .then((response) => response) 18 | .catch((error) => { 19 | console.log('ERROR from deleteCustomer controller : ', error); 20 | return 'error'; 21 | }) 22 | }; 23 | 24 | customersController.getCustomer = async (custId) => { 25 | return await customersModel.findOne(custId) 26 | .catch((error) => { 27 | console.log('ERROR from getCustomer controller : ', error) 28 | return 'error'; 29 | }) 30 | } 31 | 32 | module.exports = customersController; 33 | -------------------------------------------------------------------------------- /src/customers/customersModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | require('dotenv').config(); 3 | 4 | const { Schema } = mongoose; 5 | 6 | // DB link for customers data. 7 | const customers_db_uri = `${process.env.CUSTOMERS_DB}`; 8 | 9 | mongoose.connect(customers_db_uri, { useNewUrlParser: true, useUnifiedTopology: true }) 10 | .then(() => console.log('Connected!!!********* Customer Database is live!!!')) 11 | .catch((err) => console.log('Connection Error ', err)); 12 | 13 | // Schema for the database 14 | const CustomerSchema = new Schema({ 15 | custId: { 16 | type: Number, 17 | required: true, 18 | }, 19 | name: { 20 | type: String, 21 | required: true, 22 | }, 23 | age: { 24 | type: Number, 25 | required: true, 26 | }, 27 | address: { 28 | type: String, 29 | required: true, 30 | }, 31 | favBookId: { 32 | type: Number, 33 | required: true, 34 | }, 35 | }); 36 | 37 | // create model and ship out 38 | const customerModel = mongoose.model('customerModel', CustomerSchema); 39 | 40 | module.exports = customerModel; 41 | -------------------------------------------------------------------------------- /src/customers/customersServer.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const grpc = require('grpc'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | const HorusServerWrapper = require('@horustracer/serverwrapper'); 5 | const controller = require('./customersController.js'); 6 | 7 | const booksStub = require(path.join(__dirname, '../stubs/booksStub.js')); 8 | 9 | const PROTO_PATH = path.join(__dirname, '../protos/customers.proto'); 10 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 11 | keepCase: true, 12 | longs: String, 13 | enums: String, 14 | arrays: true, 15 | }); 16 | const customersProto = grpc.loadPackageDefinition(packageDefinition); 17 | 18 | const server = new grpc.Server(); 19 | 20 | function GetBookIdAsPromise(bookId, server) { 21 | return new Promise((resolve, reject) => { 22 | booksStub.GetBookByID(bookId, (error, response) => { 23 | if (error) resolve('error'); 24 | booksStub.makeHandShakeWithServer(server, 'GetBookByID'); 25 | resolve({ book: response }); 26 | }); 27 | }); 28 | } 29 | 30 | // The Horus Server Wrapper "wraps" each server method passed in 31 | // Replace server.addService({ .. methods}) with Const ServerWrapper = new HorusServerWrapper (serverObject, service, {..methods}) 32 | // Your preexisting methods can remain entirely the same 33 | 34 | const ServerWrapper = new HorusServerWrapper( 35 | server, 36 | customersProto.CustomersService.service, 37 | { 38 | CreateCustomer: async (call, callback) => { 39 | const result = await controller.createCustomer(call.request); 40 | 41 | if (result === 'error') { 42 | return callback({ 43 | code: grpc.status.STATUS_UNKNOWN, 44 | message: 'There was an error writing to the database', 45 | }); 46 | } 47 | callback(null, { 48 | custId: result.custId, 49 | name: result.name, 50 | age: result.age, 51 | address: result.address, 52 | favBookId: result.favBookId, 53 | }); 54 | }, 55 | DeleteCustomer: async (call, callback) => { 56 | const result = await controller.deleteCustomer(call.request.custId); 57 | 58 | if (result === 'error') { 59 | return callback({ 60 | code: grpc.status.STATUS_UNKNOWN, 61 | message: 'There was an error deleting from the database', 62 | }); 63 | } 64 | callback(null, {}); 65 | }, 66 | GetCustomer: async (call, callback) => { 67 | const customer = await controller.getCustomer(call.request); 68 | if (customer === 'error') { 69 | return callback({ 70 | code: grpc.status.STATUS_UNKNOWN, 71 | message: 'There was an error querying the database', 72 | }); 73 | } 74 | 75 | const responseFromGetBookById = await GetBookIdAsPromise( 76 | { bookId: customer.favBookId }, 77 | ServerWrapper, 78 | ); 79 | 80 | if (responseFromGetBookById === 'error') { 81 | return callback({ 82 | code: grpc.status.STATUS_UNKNOWN, 83 | message: 'There was an error making an intraservice request to books', 84 | }); 85 | } 86 | 87 | const customerWithFavBook = {}; 88 | customerWithFavBook.custId = customer.custId; 89 | customerWithFavBook.name = customer.name; 90 | customerWithFavBook.age = customer.age; 91 | customerWithFavBook.address = customer.address; 92 | customerWithFavBook.favBook = responseFromGetBookById.book; 93 | callback(null, customerWithFavBook); 94 | }, 95 | }, 96 | ); 97 | 98 | server.bind('127.0.0.1:40043', grpc.ServerCredentials.createInsecure()); 99 | console.log('customerServer.js running at http://127.0.0.1:40043'); 100 | console.log('call from customer server'); 101 | 102 | server.start(); 103 | -------------------------------------------------------------------------------- /src/customers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customers", 3 | "version": "1.0.0", 4 | "description": "npm file for customers microservice", 5 | "main": "customersServer.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Horus", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@grpc/proto-loader": "^0.5.5", 13 | "@horustracer/serverwrapper": "^1.0.3", 14 | "dotenv": "^8.2.0", 15 | "grpc": "^1.24.3", 16 | "mongoose": "^5.8.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/protos/books.proto: -------------------------------------------------------------------------------- 1 | syntax = 'proto3'; 2 | 3 | service BooksService { 4 | rpc CreateBook (Book) returns (Book) {} 5 | rpc GetBooks (Empty) returns (BookList) {} 6 | rpc GetBookByID (BookID) returns (Book) {} 7 | rpc DeleteBook (BookID) returns (Empty) {} 8 | } 9 | 10 | message Book { 11 | //title, author, # of pages, publisher 12 | string title = 1; 13 | string author = 2; 14 | int32 numberOfPages = 3; 15 | string publisher = 4; 16 | int32 bookId = 5; 17 | } 18 | 19 | message Empty {} 20 | 21 | message BookList { 22 | repeated Book books = 1; 23 | } 24 | 25 | message BookID { 26 | int32 bookId = 1; 27 | } -------------------------------------------------------------------------------- /src/protos/customers.proto: -------------------------------------------------------------------------------- 1 | syntax = 'proto3'; 2 | 3 | service CustomersService { 4 | rpc CreateCustomer (CustomerReq) returns (CustomerReq){} 5 | rpc GetCustomer (CustomerID) returns (CustomerRes) {} 6 | rpc DeleteCustomer (CustomerID) returns (Empty) {} 7 | } 8 | 9 | message CustomerReq { 10 | int32 custId = 1; 11 | string name = 2; 12 | string age = 3; 13 | string address = 4; 14 | int32 favBookId = 5; 15 | } 16 | 17 | message Book { 18 | //title, author, # of pages, publisher 19 | string title = 1; 20 | string author = 2; 21 | int32 numberOfPages = 3; 22 | string publisher = 4; 23 | int32 bookId = 5; 24 | } 25 | 26 | message CustomerRes { 27 | int32 custId = 1; 28 | string name = 2; 29 | string age = 3; 30 | string address = 4; 31 | Book favBook = 5; 32 | } 33 | 34 | message Empty {} 35 | 36 | message CustomerList{ 37 | repeated CustomerRes names = 1; 38 | } 39 | 40 | message CustomerID { 41 | int32 custId = 1; 42 | } 43 | -------------------------------------------------------------------------------- /src/stubs/booksStub.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const grpc = require('grpc'); 3 | require('dotenv').config(); 4 | const protoLoader = require('@grpc/proto-loader'); 5 | 6 | const HorusClientWrapper = require('@horustracer/clientwrapper'); 7 | 8 | const PROTO_PATH = path.join(__dirname, '../protos/books.proto'); 9 | 10 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 11 | keepCase: true, 12 | longs: String, 13 | enums: String, 14 | arrays: true, 15 | }); 16 | 17 | const BooksService = grpc.loadPackageDefinition(packageDefinition).BooksService; 18 | 19 | const client = new BooksService( 20 | 'localhost:30043', 21 | grpc.credentials.createInsecure(), 22 | ); 23 | 24 | // The Client Wrapper "wraps" all the methods within the client object 25 | // The first parameter is the actual gRPC client object, the second is the service, and the third is the output file 26 | // Your invocations of the client at whenever you export this module to can remain entirely the same 27 | 28 | const ClientWrapper = new HorusClientWrapper(client, BooksService, 'books.txt', 'main', `${process.env.HORUS_DB}`, `${process.env.SLACK_URL}`); 29 | 30 | // Export the Client Wrapper rather than the server object 31 | module.exports = ClientWrapper; 32 | -------------------------------------------------------------------------------- /src/stubs/customersStub.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const grpc = require('grpc'); 3 | require('dotenv').config(); 4 | const protoLoader = require('@grpc/proto-loader'); 5 | 6 | const HorusClientWrapper = require('@horustracer/clientwrapper'); 7 | 8 | const PROTO_PATH = path.join(__dirname, '../protos/customers.proto'); 9 | 10 | const packageDefinition = protoLoader.loadSync(PROTO_PATH, { 11 | keepCase: true, 12 | longs: String, 13 | enums: String, 14 | arrays: true, 15 | }); 16 | 17 | const CustomersService = grpc.loadPackageDefinition(packageDefinition) 18 | .CustomersService; 19 | 20 | const client = new CustomersService( 21 | 'localhost:40043', 22 | grpc.credentials.createInsecure(), 23 | ); 24 | 25 | // The Client Wrapper "wraps" all the methods within the client object 26 | // The first parameter is the actual gRPC client object, the second is the service, and the third is the output file 27 | // Your invocations of the client at whenever you export this module to can remain entirely the same 28 | const ClientWrapper = new HorusClientWrapper( 29 | client, 30 | CustomersService, 31 | 'customers.txt', 32 | 'main', 33 | `${process.env.HORUS_DB}`, 34 | `${process.env.SLACK_URL}`, 35 | ); 36 | 37 | // Export the Client Wrapper rather than the server object 38 | module.exports = ClientWrapper; 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/MockFrontEnd/index.js', 5 | target: 'node', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, './src/build'), 9 | }, 10 | mode: process.env.NODE_ENV, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?/, 15 | exclude: path.resolve(__dirname, './node_modules/'), 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['@babel/preset-env', '@babel/preset-react'], 20 | }, 21 | }, 22 | }, 23 | { 24 | test: /\.s?css$/i, 25 | exclude: path.resolve(__dirname, './node_modules/'), 26 | use: ['style-loader', 'css-loader', 'sass-loader'], 27 | }, 28 | ], 29 | }, 30 | }; 31 | --------------------------------------------------------------------------------