{"id":15363781,"url":"https://github.com/javieraviles/split-the-monolith","last_synced_at":"2025-04-15T09:27:33.838Z","repository":{"id":82659616,"uuid":"258575674","full_name":"javieraviles/split-the-monolith","owner":"javieraviles","description":"Everyday is getting more and more popular and, sometimes, worthy and useful -\u003e split a monolith into microservices. In this repo I will show an example of how to split a monolith into microservices using the Strangler Fig and Branch By Abstraction patterns.","archived":false,"fork":false,"pushed_at":"2020-04-29T14:51:59.000Z","size":192,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T18:51:47.598Z","etag":null,"topics":["branch-by-abstraction","docker","feature-toggle","h2-database","kubernetes","microservices","minikube","monolith","nginx","rest-api","split","spring-boot","strangler-fig"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/javieraviles.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-24T17:15:10.000Z","updated_at":"2023-11-04T03:05:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"d95bc6e0-a96c-44c5-944d-72822a777a7b","html_url":"https://github.com/javieraviles/split-the-monolith","commit_stats":{"total_commits":39,"total_committers":1,"mean_commits":39.0,"dds":0.0,"last_synced_commit":"99a4886fb0eeb30f5d09ce6e4f78434875ef21fc"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/javieraviles%2Fsplit-the-monolith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/javieraviles%2Fsplit-the-monolith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/javieraviles%2Fsplit-the-monolith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/javieraviles%2Fsplit-the-monolith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/javieraviles","download_url":"https://codeload.github.com/javieraviles/split-the-monolith/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249042153,"owners_count":21203225,"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":["branch-by-abstraction","docker","feature-toggle","h2-database","kubernetes","microservices","minikube","monolith","nginx","rest-api","split","spring-boot","strangler-fig"],"created_at":"2024-10-01T13:08:27.839Z","updated_at":"2025-04-15T09:27:33.822Z","avatar_url":"https://github.com/javieraviles.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# split-the-monolith\nEveryday is getting more and more popular and, **sometimes**, worthy and useful -\u003e **split a monolith into microservices**. In this repo I will show an example of how to split a monolith into microservices using the **Strangler Fig** and **Branch By Abstraction** patterns. It will take three stages. And of course, will introduce some cloud stack along the way, as this is as well something super common nowadays.\n\nThere is a whole variety of technologies out there, for this example I will use:\n - SpringBoot (microservices themselves).\n - After split -\u003e Docker, K8s, and NGINX as ingress proxy to redirect traffic to each ms.\n\nEverything will be in the master branch, having a specific `tag` for each Phase once finished.\n\n- [split-the-monolith](#split-the-monolith)\n  - [Phase 1 - THE MONOLITH](#phase-1---the-monolith)\n  - [Phase 2 - Applying STRANGLER FIG](#phase-2---applying-strangler-fig)\n    - [The new Microservice, OrdersMs](#the-new-microservice-ordersms)\n    - [The proxy, NGINX as K8s Ingress](#the-proxy-nginx-as-k8s-ingress)\n  - [Phase 3 - Applying BRANCH BY ABSTRACTION](#phase-3---applying-branch-by-abstraction)\n\n## Phase 1 - THE MONOLITH\nThis is a very simple SpringBoot project to dispatch `Orders`. Using H2 as database for simplicity, the main class will populate some data into the database on startup for testing purposes. A customer, a product and an order.\n\nThree self-explaining entities:\n - **Customer**\n   - name\n   - credit\n - **Product**\n   - name\n   - stock\n - **Order**\n   - customer (manyToOne)\n   - totalAmount\n   - product (manyToOne)\n   - productQuantity\n\nREST api to create any of the aforementioned ones in `JSON` format. Have a look at `IntegrationTest.java` to see some use-cases.\n\nCustomer and Product have to be in place before creating an order. If not enough stock in the product or credit in the customer, an exception will be thrown. The core logic of the system is in the `OrderSaga.java` class which will attempt to create an Order in one transaction.\n\nNotice a PATCH method endpoint for both `Customer` and `Product` in their controllers to update credit/stock.\n\nAdditionally, an email notification will be sent to a customer whenever the system adds some credit to the customer. Such notificatons are carried out via `NotificationService.java`. The implementation is not there, as it is not relevant here, so I will just log a message instead. BUT, it is still important the fact that the service exists as the **Branch By Abstraction** pattern will be applied over the notification feature.\n\nSimple nice and clean, why splitting right? well is just an example.\n\n## Phase 2 - Applying STRANGLER FIG\nThe goal here is to get some responsibilities out of the original monolith, so I want to extract the `orders` management into a separate microservice.\n\nBased on [Martin Fowler Strangler Fig post](https://martinfowler.com/bliki/StranglerFigApplication.html), we will \"gradually create a new system around the edges of the old\".\n\n![Strangler Fig Pattern](https://raw.githubusercontent.com/javieraviles/split-the-monolith/master/images/strangler-fig.jpg)\n\nA proxy will still forward `/customers` and `/products` api requests to the monolith, but `/orders` should then go to the new microservice.\n\nObviously, the new service will have to do some internal requests to the monolith, updating credit and stock of customers and products, and get some information; should handle orders in an independant DB, though. **The idea is we do not need to touch or modify the monolith**.\n\n### The new Microservice, OrdersMs\nA separate SpringBoot project, containing DTOs for customer and product, and one entity `Order.java`.\n\nOnly one REST controller for Order will be created here, and again the core logic of the system is in the `OrderSaga.java` class which will attempt to create an Order. We can´t use a transaction as we do in the monolith, so will use a rest client which will attempt to get credit and stock from the monolith. If something goes wrong we will need to perform a compensation now.\n\nThe idea is, even though the implementation is different, we will get the same exception for the same use cases, so the external api remains exactly the same. \"Customer and Product have to be in place before creating an order. If not enough stock in the product or credit in the customer, an exception will be thrown\".  Again have a look at `IntegrationTest.java` to see some use-cases.\n\nThe monolith url will be set thorugh an environment variable so it's more flexible for us to set it later to a different value.\n\nThis way we have a separate microservice ready to take all `/products` requests.\n\n### The proxy, NGINX as K8s Ingress\nIf you are not familiar with kubernetes, the Ingress-NGINX implementation might not seem straightforward to you, but the underlaying NGINX functionality is still the same. It will just forward traffic to one microservice or another depending on the path. Let's have a look at the `k8s/ingress.yml`:\n\n```\napiVersion: extensions/v1beta1  \nkind: Ingress  \nmetadata:  \n  name: split-the-monolith-ingress\n  annotations:\n    kubernetes.io/ingress.class: \"nginx\"\nspec:  \n  rules:\n  - host: split-the-monolith.com\n    http:\n      paths:\n      - path: /customers\n        backend:\n          serviceName: monolith\n          servicePort: 8080\n      - path: /products\n        backend:\n          serviceName: monolith\n          servicePort: 8080\n      - path: /orders\n        backend:\n          serviceName: ordersms\n          servicePort: 8090\n```\n\n`Monolith` and `ordersms` have already been declared as services running in k8s respectively in `k8s/monolith.yaml` amd `k8s/ordersms.yaml`. I'm using minikube with Ingress addon as local dev environment, and setting `split-the-monolith.com` to my VM ip in my hosts file.\n\nPlease note that both services had also been Dockerized including a `Dockerfile` into each project and it's been pushed to dockerhub so k8s yaml images can find them somewhere.\n\nNow is all set, functionality remains the same as in the monolith, and we have two microservices, each one taking care of a task, all behind a proxy, nice!!\n\nIn order to have some **\"end to end\"** tests, not only integration tests on each microservice, I've added a Postman collection in the `e2e` directory, which can be run against a classic local environment, starting up both microservices from your IDE in localhost (here tell postman collection to use the `env/LocalDev.json` environment file) or against a local k8s using ingress (here tell postman collection to use the `env/K8sDev.json` environment file).\n\nUse Newman to easily execute them:\n\n```\nnpm install -g Newman\n\nnewman run e2e/split-the-monolith.postman_collection.json -k -e e2e/env/K8sDev.postman_environment.json\n\n```\n\n## Phase 3 - Applying BRANCH BY ABSTRACTION\nRemember the `NotificationService` in the monolith? well, that could very well be another microservice, just in charge of sending notifications, so the monolith does not need to have such responsibility anymore. Would be nice to spin up the new microservice and gradually switch notifications generation from monolith to this new service, using a [feature toggle](https://martinfowler.com/articles/feature-toggles.html).\n\nBased on [Martin Fowler Branch by Abstraction post](https://martinfowler.com/bliki/BranchByAbstraction.html), \"While we are building the new feature we can use FeatureToggles to run the new supplier in test environments and compare its behavior to the flawed supplier\".\n\n![Branch by Abstraction Pattern](https://raw.githubusercontent.com/javieraviles/split-the-monolith/master/images/branch-by-abstraction.png)\n\nAnd so we will do in this phase 3. After creating another SpringBoot project, the simplest one (`NotificationController` to receive a POST for notifications creation, calling a service that actually sends the notification), we will then introduce a `feature toggle` in the monolith. This way, when some credit is added to a user, depending on the value of the feature toggle, the notification will get sent through the monolith implementation (still there) or through the new service (the monolith will trigger a POST to /notificationsmsUrl) so the new microservice takes care of this.\n\nFor us the feature toggle will just be an application.property called `use.notification.service`, containing a boolean:\n\n```\n@Value(value = \"${use.notification.service}\")\nprivate boolean useNotificationService;\n\n...\n\n\nif (useNotificationService) {\n  // rest call to new microservice\n  notificationsMsClient.sendNotification(notification);\n} else {\n  // monolith sends the notificaton itself\n  notificationService.sendEmailNotification(notification);\n}\n```\n\nThis way, the k8s deployment yaml for the monolith will now contain an environment variable that will override the feature toggle value per environment:\n\n```\n- env:\n  - name: NOTIFICATIONSMS_URL\n    value: http://notificationsms:8070\n  - name: USE_NOTIFICATION_SERVICE\n    value: \"true\"\n  image: javieraviles/monolith\n  name: monolith\n  imagePullPolicy: Always\n  ports:\n    - containerPort: 8080\n  securityContext:\n    allowPrivilegeEscalation: false\n```\n\nSo operations will have now the option to configure whether to use this external notifications service or not from there. The idea is to reach a point where no more clients are using the original monolith notifications feature so it can be removed.\n\nAn additional `k8s/notificationsms.yaml` file has been added to deploy the new microservice. Please note the `type: ClusterIP` in there, as this notifications microservice does not need to get any external traffic, will only receieve internal requests.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjavieraviles%2Fsplit-the-monolith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjavieraviles%2Fsplit-the-monolith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjavieraviles%2Fsplit-the-monolith/lists"}