- SOTA Embedding Retrieval: Gemini + pgvector for Production Chat
- A Review of Agentic Design Patterns
- Model Context Protocol (MCP) and MCP Servers in LLM Agent Systems
- Building AI Agents for Automated Multi-Format Content: From News to Podcasts
- Rediscovering Cursor
- GraphRAG > Traditional Vector RAG
- Cultural Bias in LLMs
- Mapping out the AI Landscape with Topic Modelling
- Sustainable Cloud Computing: Carbon-Aware AI
- Defensive Technology for the Next Decade of AI
- Situational Awareness: The Decade Ahead
- Mechanistic Interpretability: A Survey
- Why I Left Ubuntu
- Multi-Agent Collaboration
- Embeddings and Vector Databases: Enhancing Retrieval Systems
- Building an Automated Newsletter-to-Summary Pipeline with OpenAI: Zapier AI Actions vs AWS SES & Lambda
- Local AI Image Generation
- MLOps: Deploying a Distributed Ray Python Server with Kubernetes, EKS & KubeRay
- Making the Switch to Linux for Development: A Developer's Experience
- Scaling Options Pricing with Ray
- The Async Worker Pool
- Browser Fingerprinting: Introducing My First NPM Package
- Reading Data from @socket.io/redis-emitter without Using a Socket.io Client
- Socket.io Middleware for Redux Store Integration
- ›Sharing TypeScript Code Between Microservices: A Guide Using Git Submodules
- Efficient Dataset Storage: Beyond CSVs
- Embracing Next.js 13: Why I switched from Plain React
- Deploy & Scale Socket.io Containers in ECS with Elasticache
- Implementing TOTP Authentication in Python using PyOTP
- Simplifying Lambda Layer ARNs and Creating Custom Layers in AWS
- TimeScaleDB Deployment: Docker Containers and EC2 Setup
- How to SSH into an EC2 Instance Using PuTTY
Sharing TypeScript Code Between Microservices: A Guide Using Git Submodules
In a microservices architecture, sharing code among different services is a common challenge that developers face. Often, there are utility functions or modules that are useful across multiple microservices, and duplicating this code in each project leads to maintenance overhead and potential inconsistencies. In this article, we'll explore how to efficiently share TypeScript code between microservices using Git submodules.
Introducing Git Submodules
Git submodules allow us to include one Git repository as a subdirectory within another repository. By leveraging this feature, we can create a separate repository for shared code and have microservice repositories include it as a submodule. This approach has several advantages:
-
Modularity: Each microservice can control when to update to a new version of the shared code by pointing to a specific commit of the submodule.
-
Separate Repository for Shared Code: The shared code repository doesn't need to have its own
package.json
. It can solely consist of TypeScript files, making it lightweight and dedicated to utility functions.
Step-by-Step Guide
-
Create a Shared Code Repository: Start by creating a new Git repository specifically for the shared code. For instance, you could name it
utils-{shared code name}
. -
Create Microservice Repositories: Next, create separate repositories for each microservice you want to develop. For example, you might have
{microservice name}
for one of the microservices. -
Add Git Submodule to Microservice Repository: In the microservice repository, create a
utils
folder where we'll include the shared code as a submodule.- Use the following command to add the Git submodule:
git submodule add git@github.com:xxx ./src/utils/kibana-logger-ts
- This command links the
kibana-logger-ts
submodule to the./src/utils/kibana-logger-ts
directory within the microservice repository.
- Use the following command to add the Git submodule:
-
Configure ts-node for Development: In my code at least, I configure
ts-node
for development in thetsconfig.json
:- Run
npm i -D tsconfig-paths
- Add the following to the
tsconfig.json
:
- Run
"ts-node": {
"require": ["tsconfig-paths/register"]
},
- Path Mapping in tsconfig.json: Modify the
tsconfig.json
of the microservice to include path mappings aliases for the shared code. Rename the import as to how we want to use the util.- This makes the code from the
src
folder of the microservice repository access any submodule package easily.
- This makes the code from the
{
"compilerOptions": {
...
"baseUrl": "src",
...
"paths": {
"@kibana-logger": ["utils/kibana-logger-ts/src"],
}
},
...
}
Now we will import the shared code like:
import { KibanaFactory } from '@kibana-logger';
Another option here is to create an alias for the entire utils folder and import what we need from there:
{
"@utils/*": ["utils/*"]
}
Here the imports will be like:
import { KibanaFactory } from '@utils/kibana-logger-ts/src';
With this option, we will not need to manually update the tsconfig.json
every time we add a new submodule.
Handling Production Build with Babel
The above works in dev. From the TypeScript docs, note that this Path-mapping feature does not change how import paths are emitted by tsc
, so paths should only be used to inform TypeScript that another tool has this mapping and will use it at runtime or when bundling. For prod, we need to add these aliases into the built JavaScript code.
We could run the microservice with ts-node
in prod, but this is not recommended:
{
...
"start": "npm run build && node -r ts-node/register/transpile-only -r tsconfig-paths/register build/index.js",
...
}
I have chosen instead to use Babel to transpile the code.
- Install needed packages for dev:
npm i -D @babel/cli babel-plugin-module-resolver
- Created a
.babelrc
in the root of your project:
{
"compact": false,
"retainLines": true,
"minified": false,
"inputSourceMap": false,
"sourceMaps": false,
"plugins": [
[
"module-resolver",
{
"alias": {
"@kibana-logger": "/utils/kibana-logger-ts/src"
}
}
]
]
}
Also, add this to your Dockerfile to copy the .babelrc file during the initial build:
COPY .babelrc ./
Now, your microservice can be built and run with Babel in the production environment; it will work in the container built by Docker.
Making Changes to the Shared Code
When it's time to make changes to the shared code, you have two options:
- Option 1: Clone the shared util code separately, make the necessary changes, and push them up. Then in the microservice repository, run
git submodule update --remote
to update the submodule to the latest commit. - Option 2: Modify the submodule directly into the microservice. Then, push changes from submodule, and the changes will be available to everyone using the shared-code repository.
Managing External Dependencies
To handle external dependencies in the shared code, we have two options:
- Option 1: Install the dependencies in the shared code repository, and then add the dependencies to the microservice repository.
- Perform
npm install
in the shared code repository. - Then running
npm start
in the microservice works fine as it can compile and finds thenode_modules
directory of the submodule
- Perform
There are some scenarios, e.g. graphql
lib, that has a runtime check to ensure a unique dependency on the library. Relying on the two node_modules
causes a runtime (not compilation time) error. Hence, Option 2 might be the only possible one here.
- Option 2: Install Child Dependencies into Parent Dependencies.
- Can use the
npm install
withfile:
to add the dependencies manually. npm install --save file:utils/utils-{shared code name}
- Most of our repositories have about the same dependencies. So it is manageable to rely on manually taking the dependencies.
- Can use the
I personally use Option 2.
Additional Notes
Here are some additional notes and tips to keep in mind when working with Git submodules:
-
Initializing Submodules: When cloning a project from Git for the first time, the submodules are not automatically initialized. To fetch the submodule's Git repositories, run the command
git submodule update --init
at the root of the microservice project. -
.gitmodules File: When you perform the
git submodule
command, it creates a hidden file called.gitmodules
within the parent repository. This file keeps track of the submodule's URL and path. -
Committing Changes in Submodules: Remember that performing
git add .
at the root of the microservice project only adds the files from the parent. Howevergit status
at the root of the parent repository shows the changes with the submodule changes. To commit and push changes for the submodule, you need to navigate to the submodule folder and perform the git commands.