{"id":19777018,"url":"https://github.com/madeindra/codeigniter-microservice","last_synced_at":"2025-04-30T19:31:24.682Z","repository":{"id":38166489,"uuid":"244260409","full_name":"madeindra/codeigniter-microservice","owner":"madeindra","description":"Microservice in PHP CodeIgniter 3 \u0026 RabbitMQ AMQP using Saga Pattern and Test-Driven Development","archived":false,"fork":false,"pushed_at":"2022-06-21T18:05:45.000Z","size":1279,"stargazers_count":20,"open_issues_count":2,"forks_count":29,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-06T04:26:11.428Z","etag":null,"topics":["amqp","codeigniter3","microservices","php","rabbit-mq","saga","test-driven-development"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/madeindra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-02T02:16:46.000Z","updated_at":"2025-03-05T17:58:18.000Z","dependencies_parsed_at":"2022-09-15T14:41:49.456Z","dependency_job_id":null,"html_url":"https://github.com/madeindra/codeigniter-microservice","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madeindra%2Fcodeigniter-microservice","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madeindra%2Fcodeigniter-microservice/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madeindra%2Fcodeigniter-microservice/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madeindra%2Fcodeigniter-microservice/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/madeindra","download_url":"https://codeload.github.com/madeindra/codeigniter-microservice/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251769323,"owners_count":21640883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amqp","codeigniter3","microservices","php","rabbit-mq","saga","test-driven-development"],"created_at":"2024-11-12T05:22:49.434Z","updated_at":"2025-04-30T19:31:20.814Z","avatar_url":"https://github.com/madeindra.png","language":"PHP","readme":"# TDD Microservice in PHP\n## Introduction\nThis project is a proof-of-concept of Microservice Architecture build using CodeIgniter 3 with Test Driven Development in mind.  \n\nThere are three main decoupled service in this projects: Financial service, Order service, and Warehouse service. Each services has its own MySQL database, in case a service need data from another service, the service will do the checking using Helper that pass message through RabbitMQ AMQP Queue. All these dependency are already packed in one docker-compose.\n\n## Prerequiste\n- Docker\n- Docker Compose\n- Composer\n- Apache HTTP Server (Optional, already bundled in docker-compose)\n- PHP 7.3 (Optional, already bundled in docker-compose)\n- MySQL 8.0 (Optional, already bundled in docker-compose)\n- RabbitMQ (Optional, alraedy bundled in docker-compose)\n\n## Running\nNormally, to run PHP application we just need to access it from browser by entering the address and the browser will render the page according to the instructions coded. To enable the worker subscribtion to the queue, it need to run continously, thus the worker run by using CLI. Worker initialization is done automatically if using docker-compose, if not then start the worker by running the start_worker.sh script on each service directory. \n\n**Install dependency using script (recommended)**\n```\nchmod +x ./install-dependency.sh\n./install-dependency.sh\n```\n\n**Install dependency manually (If you are unable to run the script)**\n```\ncd financial\ncomposer install\ncd ..\ncd order\ncomposer install\ncd ..\ncd warehouse\ncomposer install\ncd ..\n```\n\n**Start with docker (recommended)**\n```\ndocker-compose up\n```\n\nphp container will fail several time, it is normal, because it is waiting for the RabbitMQ container to be up.\n\nIf containers are run succesfully, you will see two lines of messages like shown bellow, that means the worker already started subscribing to the queue.\n```\n[x] Awaiting message\n``` \n\n## Database Migration\nTo start database migration,access each service with migrate route (by default the services run in port 8081, 8082, and 8083)\n```http\nlocalhost:8081/tdd-microservice-poc/index.php/migrate\nlocalhost:8082/tdd-microservice-poc/index.php/migrate\nlocalhost:8083/tdd-microservice-poc/index.php/migrate\n```\n\n## API\n### Financial Service\n#### Get list of invoices\n```http\nGET localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices\n```\n\n#### Create a new invoice\n(Usually called by helper after order check out)\n```http\nPOST localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices\n```\n\nRequest Body\n```json\n{\n    \"id\": \"1\",\n    \"order_id\": \"1\",\n    \"total\": \"10000\",\n    \"status\": \"incomplete\"\n}\n```\n`id` is optional, it is auto-increased from previous data in the table\n#### Get an invoice\n\n```http\nGET localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices/{invoice_id}\n```\n\n#### Update an invoice \n```http\nPUT localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices/{invoice_id}\n```\n\nRequest Body\n```json\n{\n    \"status\": \"waiting\"\n}\n```\n\n#### Delete an invoice\n```http\nDELETE localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices/{invoice_id}\n```\n\n#### Update an invoice by order id\n(Usually called by helper after confirming stock exist)\n```http\nPUT localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices/orders/{order_id}\n```\n\nRequest Body\n```json\n{\n    \"status\": \"waiting\"\n}\n```\n\n#### Delete an invoice by order id\n(Usually called by helper after failing to confirm stock exist)\n```http\nDELETE localhost:8081/tdd-microservice-poc/index.php/api/v1/invoices/{invoice_id}\n```\n\n### Order Service\n#### Get list of orders\n```http\nGET localhost:8082/tdd-microservice-poc/index.php/api/v1/orders\n```\n\n#### Create a new order\n```http\nPOST localhost:8082/tdd-microservice-poc/index.php/api/v1/orders\n```\nRequest Body\n```json\n{\n    \"id\": \"1\",\n    \"product_id\": \"1\",\n    \"quantity\": \"5\",\n    \"price\": \"5000\"\n}\n```\n`id` is optional, it is auto-increased from previous data in the table\n\n#### Get an order\n```http\nGET localhost:8082/tdd-microservice-poc/index.php/api/v1/orders/{order_id}\n```\n\n#### Update an order\n```http\nPUT localhost:8082/tdd-microservice-poc/index.php/api/v1/orders/{order_id}\n```\n\nRequest Body\n```json\n{\n    \"quantity\": \"10\",\n    \"price\": \"500\"\n}\n```\n\n#### Delete an order\n```http\nDELETE localhost:8082/tdd-microservice-poc/index.php/api/v1/orders/{order_id}\n```\n\nRequest Body\n```json\n(Not required)\n```\n\n#### Checkout an order to create it invoice and update the stock\n(Will create invoice and check if products is in stock)\n```http\nPOST localhost:8082/tdd-microservice-poc/index.php/api/v1/orders/checkout/{order_id}\n```\n\n### Warehouse Service\n#### Get list of products\n```http\nGET localhost:8083/tdd-microservice-poc/index.php/api/v1/products\n```\n\n#### Create a new product\n```http\nPOST localhost:8083/tdd-microservice-poc/index.php/api/v1/products\n```\n\nRequest Body\n```json\n{\n    \"id\": \"1\",\n    \"stock\": \"10\",\n    \"price\": \"500\"\n}\n```\n`id` is optional, it is auto-increased from previous data in the table\n\n#### Get a product\n```http\nGET localhost:8083/tdd-microservice-poc/index.php/api/v1/products/{product_id}\n```\n\n#### Update a product\n```http\nPUT localhost:8083/tdd-microservice-poc/index.php/api/v1/products/{product_id}\n```\n\nRequest Body\n```json\n{\n    \"stock\": \"10\",\n    \"price\": \"500\"\n}\n```\n\n#### Delete a product\n```http\nDELETE localhost:8083/tdd-microservice-poc/index.php/api/v1/products/{product_id}\n```\n\n#### Update stock of a product\n```http\nPUT localhost:8083/tdd-microservice-poc/index.php/api/v1/products/stocks/{product_id}\n```\n\nRequest Body (Decrese stock by 10)\n```json\n{\n    \"quantity\" : \"-10\"\n}\n```\n\nRequest Body (Increase stock by 10)\n```json\n{\n    \"quantity\" : \"+10\"\n}\n```\n\n## Architecture\nHere is the architecture for the microservice (if it fails to show, you can open the `TDD Microservice.xml` in draw.io)\n\n![Diagram](./Diagram.png)\n\n- API Gateway on the diagram is not included in this repository, but it's not necessary for this repository to work\n\n- Each outlined rectangle is a separate Docker Container.\n\n- Service is written in PHP, but it can be changed as RabbitMQ is Language-Agnostic.\n\n- RabbitMQ can only receive and send a message in String format, it is possible to receive and send JSON string.\n\n- Traditionally, PHP application runs in a Web Server, it can't be used to receive the message through a socket, thus a PHP Service needs one or more Worker(s) to receive message. The worker is optionally used in sending a message as sending can be simply done using a conventional method/function.\n\n- Exchange is used when a message is needed to be sent to more than one Queue.\n\n- Exchange can route a message conditionally, but in this scenario, it only use Direct Routing, thus it will send all the message to all the Queue bonded to it.\n\n- Processes with (a) and (b) is processed separately without waiting for the other Service (asynchronously).\n\n- RabbitMQ can be used as gRPC, but the process will be synchronous, it is not fit to be used in this scenario.\n\n## How It Works\n**Start** - A User wants to check-out their selected order, User hit the UI and Request (orderId 1) sent by API Gateway to Order Service.\n\n**1** - Order Service fetches the Order Detail of (orderId 1), Order Service get (productId 1, quantity 10) detail.\n\n**2** - Order Service pass (orderId 1, productId 1, quantity 10) to Order Worker to create invoice and to check if the product is in stock.\n\n**3** - Order Worker sends a message (orderId 1, productId 1, quantity 10) to Exchange 1.\n\n**4a,4b** - Exchange 1 received message (orderId 1, productId 1, quantity 10), as it is a Direct Exchange, it forwards the message to Subscribing Queue(s).  \n\n**5a** - Financial Worker received a message (orderId 1, productId 1, quantity 10), then Financial Worker passes the message to Financial Service to be processed.\n\n**5b** - Warehouse Worker received a message (orderId 1, productId 1, quantity 10), then Warehouse Worker pass only (productId 1, quantity 10) to Warehouse Service to be checked.\n\n**6a** - Financial Service creates an Invoice based on the message (orderId 1, productId 1, quantity 10).\n\n**6b** - Warehouse Service prepares a query to check stock based on the productId 1.\n\n**7a** - Financial Service successfully created the Invoice on Financial Database.\n\n**7b** - Warehouse Service fetches stock from Warehouse Database.\n\n**8a** - Financial Service tells Financial Worker that it has created the Invoice.\n\n**8b** - Warehouse Service gets information (stock 50) and compares it to quantity 10, because (stock \u003e quantity), the order can be processed, send (productId 1, quantity 10, inStock true) to Warehouse Worker.\n\n**9** - Warehouse Worker sends a message (productId 1, quantity 10, inStock true) to Financial Queue.\n\n**10** - Financial Worker receives a message (productId 1, quantity 10, inStock true) stored in Financial Queue.\n\n**11** - Financial Worker then passes the message to Financial Service to be processed.\n\n**12** - Financial Service updates the Invoice status.\n\n**13** - Financial Service tells Financial Worker that it has updated the Invoice.\n\n**14** - Financial Worker sends a message (productId 1, quantity 10, invoice true) to Exchange 2.\n\n**15a, 15b** - Exchange 2 received a message (productId 1, quantity 10, invoice true), as it is a Direct Exchange, it forwards the message to Subscribing Queue(s).  \n\n**16a** - Order Worker received a message (productId 1, quantity 10, invoice true), then Order Worker passes the message to Order Service to be processed.\n\n**16b** - Warehouse Worker received a message (productId 1, quantity 10, invoice true), then Warehouse Worker passes the message to Warehouse Service to be processed.\n\n**17a** - Order Worker completes the user's check-out process.\n\n**17b** - Warehouse Service prepares a query to decrease the product's stock based on the message (productId 1, quantity 10, invoice true).\n\n**18** - Warehouse Service decreases the stock of the product in the Warehouse Database.\n\n**19** - Warehouse Service tells Warehouse Worker that it has updated the Stock.\n\n**End** - Order Worker returns the detail of the check-out process.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmadeindra%2Fcodeigniter-microservice","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmadeindra%2Fcodeigniter-microservice","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmadeindra%2Fcodeigniter-microservice/lists"}