You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Microservice-driven architecture, or simply microservices, is an architectural style that structures an application as a collection of loosely coupled services.
Similarly, microservices are not special classes, they can also depend on other microservices as well. As an example, the following `MainWindowService` also depends on all sorts of different microservices:
@@ -66,6 +69,7 @@ class MainWindowService implements IMainWindowService {
66
69
From a framework perspective, I can construct `MainWindowService` as follows:
67
70
* Regardless of the specific microservice instance passed through the framework, as long as that instance implements the corresponding interface, the system will function seamlessly.
68
71
* For instance, as long as `FileService`, `TestFileService`, and `NullFileService` have all implemented the interface `IFileService`, the system will operate without issues.
72
+
69
73
```ts
70
74
// normal case
71
75
const mainWindowService =newMainWindowService(
@@ -106,6 +110,7 @@ Let's address some potential questions that might arise so far:
106
110
*`InstantiationService` is a concrete solution to achieve the **Dependency Injection (DI) principle**.
107
111
108
112
To illustrate how `InstantiationService` operates, consider the following API example:
@@ -124,6 +129,7 @@ Don’t worry, I will explain these concepts to you step by step.
124
129
125
130
## 1. What Is a Dependency Tree?
126
131
A dependency tree represents a hierarchical relationship between different modules (or classes). Recall the previous example, `ApplicationInstance`, its dependency tree would look like the following:
132
+
127
133
```
128
134
ApplicationInstance
129
135
├─ IInstantiationService
@@ -138,6 +144,7 @@ ApplicationInstance
138
144
├─ IMainLifecycleService
139
145
└─ IEnvironmentService
140
146
```
147
+
141
148
## 2. How Do We Determine a Dependency Tree?
142
149
Following the earlier example, where `MainWindowService` depends on `IFileService`. We can draw a few conclusions about `IFileService`:
143
150
*`IFileService` serves as an abstraction. `IFileService` as an interface, is a syntax sugar from TypeScript, which only exists on compile-time.
@@ -152,6 +159,7 @@ VSCode provide a useful function, `createDecorator`, which creates a unique deco
152
159
> Sidenote: In TypeScript, a decorator is essentially a function.
153
160
154
161
* The decorator created by `createDecorator`, acts like an identifier, which can be stored in DI to make a connection between the microservice and the concrete class implementation as follows:
162
+
155
163
```ts
156
164
// fileService.ts
157
165
const IFileService =createDecorator('file-service'); // note: a variable in TypeScript can have the same name as an interface.
@@ -170,6 +178,7 @@ const instantiationService = new InstantiationService();
170
178
// DI registration (⭐)
171
179
instantiationService.register(IFileService, newFileService()); // we've seen this line of code before
172
180
```
181
+
173
182
The decorator performs two functionalities in our cases:
174
183
1. First, since the decorator is a variable, thus it exists in run-time, it can be used in our DI system (`InstantiationService`). As previously mentioned, It establishes a connection between the abstraction concept (`IFileService`) and a concrete implementation (our case is `FileService`), as we've just done in the above example.
175
184
* At line 16, the DI system now recognizes an abstraction (`IFileService`), and a way to construct its corresponding class (`FileService`).
@@ -181,6 +190,7 @@ The decorator performs two functionalities in our cases:
181
190
182
191
### 2.2 Build a Dependency Tree at Runtime Using Decorator
@@ -245,6 +260,7 @@ A decorator, as the name tells, is used to add something extra to a class parame
245
260
> ]
246
261
> */
247
262
>```
263
+
>
248
264
> **In essence, the decorator's job is to create and store the above dependency tree at runtime.**
249
265
250
266
Continuing our example, the complete dependency tree of `MainWindowService` will look like the following:
@@ -275,6 +291,7 @@ Now, we already covered a way to create a dependency tree for our classes. We fi
275
291
For every dependency tree, its tree leaf (the dependency that depends on nothing) cannot be constructed automatically. **It means the leaf must be provided by ourselves**. We call this the registration process.
@@ -306,6 +324,7 @@ After all the dependencies have been constructed, the `InstantiationService` use
306
324
Recall the second step when assembling in `InstantiationService`, where it checks 'if the dependency has not been constructed yet'.
307
325
308
326
One might wonder how a microservice can be registered into the DI system without actually constructing one. For that, VSCode introduce a utility tool named `SyncDescriptor`:
327
+
309
328
```ts
310
329
exportclassSyncDescriptor<T> {
311
330
@@ -321,6 +340,7 @@ export class SyncDescriptor<T> {
321
340
}
322
341
```
323
342
With the help of `SyncDescriptor`, we can achieve lazy loading when registering a microservice:
0 commit comments