Skip to content

Commit 309e688

Browse files
authored
Merge pull request #194 from weaviate/feat/async-di
2 parents fb1a79e + 64b2b62 commit 309e688

34 files changed

+3905
-671
lines changed

docs/CLIENT_INIT.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,201 @@ Client initialization is asynchronous because:
100100
3. **Thread Safety**: Async operations don't block thread pool threads, improving application responsiveness
101101
4. **Error Handling**: Connection and authentication issues surface immediately during client creation, not later during operations
102102

103+
## Initialization Lifecycle
104+
105+
### Builder Pattern (Recommended)
106+
107+
When using `WeaviateClientBuilder` or `Connect` helpers, initialization happens automatically:
108+
109+
```csharp
110+
// Client is fully initialized before being returned
111+
var client = await WeaviateClientBuilder.Local().BuildAsync();
112+
// ✅ RestClient and GrpcClient are ready to use
113+
var results = await client.Collections.Get("Article").Query.FetchObjects();
114+
```
115+
116+
**Key Points:**
117+
- `BuildAsync()` calls `InitializeAsync()` internally before returning
118+
- The client is **always fully initialized** when you receive it
119+
- No manual initialization needed
120+
121+
### Dependency Injection Pattern
122+
123+
When using dependency injection, there are two modes:
124+
125+
#### Eager Initialization (Default - Recommended)
126+
127+
```csharp
128+
services.AddWeaviateLocal(
129+
hostname: "localhost",
130+
eagerInitialization: true // Default
131+
);
132+
```
133+
134+
**How it works:**
135+
- A hosted service (`WeaviateInitializationService`) runs on application startup
136+
- Calls `InitializeAsync()` automatically before your app serves requests
137+
- The client is **always initialized** when injected into services
138+
139+
```csharp
140+
public class MyService
141+
{
142+
private readonly WeaviateClient _client;
143+
144+
public MyService(WeaviateClient client)
145+
{
146+
// ✅ Client is already initialized by the hosted service
147+
_client = client;
148+
}
149+
150+
public async Task DoWork()
151+
{
152+
// ✅ Safe to use immediately
153+
var results = await _client.Collections.Get("Article").Query.FetchObjects();
154+
}
155+
}
156+
```
157+
158+
#### Lazy Initialization (Manual Control)
159+
160+
```csharp
161+
services.AddWeaviateLocal(
162+
hostname: "localhost",
163+
eagerInitialization: false // Opt-in to lazy initialization
164+
);
165+
```
166+
167+
**How it works:**
168+
- Client is created but NOT initialized
169+
- You must call `InitializeAsync()` before first use
170+
- Useful for scenarios where you want to control when initialization happens
171+
172+
```csharp
173+
public class MyService
174+
{
175+
private readonly WeaviateClient _client;
176+
177+
public MyService(WeaviateClient client)
178+
{
179+
// ⚠️ Client is NOT yet initialized
180+
_client = client;
181+
}
182+
183+
public async Task Initialize()
184+
{
185+
// ✅ Manually trigger initialization
186+
await _client.InitializeAsync();
187+
}
188+
189+
public async Task DoWork()
190+
{
191+
// Check if initialized
192+
if (!_client.IsInitialized)
193+
{
194+
await _client.InitializeAsync();
195+
}
196+
197+
var results = await _client.Collections.Get("Article").Query.FetchObjects();
198+
}
199+
}
200+
```
201+
202+
### Checking Initialization Status
203+
204+
Use the `IsInitialized` property to check if the client is ready:
205+
206+
```csharp
207+
if (client.IsInitialized)
208+
{
209+
// Safe to use RestClient and GrpcClient
210+
var results = await client.Collections.Get("Article").Query.FetchObjects();
211+
}
212+
else
213+
{
214+
// Must call InitializeAsync() first
215+
await client.InitializeAsync();
216+
}
217+
```
218+
219+
**What happens during initialization:**
220+
1. Token service is created (for authentication)
221+
2. REST client is configured
222+
3. Server metadata is fetched (validates auth, gets gRPC settings)
223+
4. gRPC client is configured with server metadata
224+
5. `RestClient` and `GrpcClient` properties are populated
225+
226+
### Important: When is the Client Ready?
227+
228+
| Pattern | When Ready | RestClient/GrpcClient Available |
229+
|---------|-----------|--------------------------------|
230+
| `await BuildAsync()` | Immediately after return | ✅ Yes |
231+
| DI Eager (default) | Before app starts serving | ✅ Yes |
232+
| DI Lazy | After calling `InitializeAsync()` | ⚠️ Only after init |
233+
234+
**⚠️ Using uninitialized client causes `NullReferenceException`:**
235+
236+
```csharp
237+
// ❌ BAD: Lazy DI without calling InitializeAsync()
238+
var client = serviceProvider.GetService<WeaviateClient>();
239+
var results = await client.Collections.Get("Article").Query.FetchObjects(); // NullReferenceException!
240+
241+
// ✅ GOOD: Check and initialize if needed
242+
var client = serviceProvider.GetService<WeaviateClient>();
243+
if (!client.IsInitialized)
244+
{
245+
await client.InitializeAsync();
246+
}
247+
var results = await client.Collections.Get("Article").Query.FetchObjects();
248+
```
249+
250+
### Automatic Initialization Guards
251+
252+
**✨ Safety Feature:** All public async methods in the Weaviate client automatically ensure initialization before executing. This provides a safety net against accidental use of uninitialized clients.
253+
254+
**How it works:**
255+
256+
When you call any async method on the client (like `Collections.Create()`, `Cluster.Replicate()`, `Alias.Get()`, etc.), the client automatically calls `EnsureInitializedAsync()` internally before performing the operation. If the client isn't initialized yet:
257+
258+
- **With eager initialization (default)**: The guard passes immediately since the client is already initialized
259+
- **With lazy initialization**: The guard triggers initialization automatically on first use
260+
261+
```csharp
262+
// Lazy DI - initialization happens automatically on first call
263+
services.AddWeaviateLocal(hostname: "localhost", eagerInitialization: false);
264+
265+
// Later in your code...
266+
var client = serviceProvider.GetService<WeaviateClient>();
267+
268+
// ✅ This works! The client auto-initializes on first use
269+
var collections = await client.Collections.List(); // Initialization happens here automatically
270+
```
271+
272+
**Performance Impact:**
273+
274+
- **Eager initialization (default)**: No overhead - guards pass through immediately
275+
- **Lazy initialization**: Minimal overhead - first call triggers initialization, subsequent calls pass through
276+
- Guards use `Lazy<Task>` internally, ensuring initialization happens exactly once even with concurrent calls
277+
278+
**When guards help:**
279+
280+
- Forgetting to call `InitializeAsync()` in lazy initialization scenarios
281+
- Race conditions where multiple threads access an uninitialized client
282+
- Unit tests that don't properly set up client initialization
283+
284+
**What's protected:**
285+
286+
-`client.Collections.*` - All collection operations
287+
-`client.Cluster.*` - All cluster operations
288+
-`client.Alias.*` - All alias operations
289+
-`client.Users.*`, `client.Roles.*`, `client.Groups.*` - All auth operations
290+
- ✅ Collection-level operations like `collection.Delete()`, `collection.Iterator()`
291+
- ✅ All async methods that access REST or gRPC clients
292+
293+
**What's not protected:**
294+
295+
- ❌ Synchronous property accessors (design limitation - properties can't be async)
296+
- ❌ Direct access to internal clients (not part of public API)
297+
103298
## Connection Configuration
104299

105300
### Local Development

0 commit comments

Comments
 (0)