Skip to content

Commit dfcbda4

Browse files
committed
feat: enhance documentation for component lifecycle methods and message handling
1 parent 1e9f3cd commit dfcbda4

10 files changed

Lines changed: 451 additions & 131 deletions

File tree

src/iop/__init__.py

Lines changed: 137 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -117,46 +117,137 @@ class OutboundAdapter(_OutboundAdapter):
117117

118118

119119
class BusinessService(_BusinessService):
120-
"""Inbound production entry point.
120+
"""Purpose:
121+
Inbound production entry point for messages entering an IoP production.
121122
122-
Use for message-driven services or Python services that receive data and
123-
send messages into a production. For polling services, prefer
124-
PollingBusinessService. For task recipes, see
125-
docs/cookbooks/add-polling-service.md and docs/cookbooks/hl7v2-native-input.md.
123+
Use when:
124+
External data, an adapter, or custom code must send a message into the
125+
production graph.
126+
127+
Lifecycle:
128+
IRIS calls on_process_input(); the default implementation delegates to
129+
on_process_input(request).
130+
131+
Best practices:
132+
Declare outbound routes with target() and wire them in a Production
133+
graph. Use PollingBusinessService for scheduled Python polling.
134+
135+
Common mistakes:
136+
Do not put startup work in __init__(); use on_init(). Do not instantiate
137+
downstream components directly.
138+
139+
Minimal example:
140+
class FileIn(BusinessService):
141+
Output = target()
142+
143+
def on_process_input(self, request):
144+
self.send_request_async(self.Output, request)
145+
146+
Related:
147+
docs/cookbooks/add-polling-service.md,
148+
docs/cookbooks/hl7v2-native-input.md
126149
"""
127150

128151
pass
129152

130153

131154
class PollingBusinessService(_PollingBusinessServiceMixin, BusinessService):
132-
"""Scheduled inbound service called by the default IRIS inbound adapter.
155+
"""Purpose:
156+
Scheduled Python service called by the default IRIS inbound adapter.
157+
158+
Use when:
159+
A production must poll an API, directory, queue, database, or other
160+
source from Python.
161+
162+
Lifecycle:
163+
IRIS calls on_process_input(); the mixin delegates that call to
164+
on_poll().
165+
166+
Best practices:
167+
Put one polling cycle in on_poll(). Declare outbound routes with
168+
target() and send messages with send_request_async(...).
169+
170+
Common mistakes:
171+
Do not block forever inside on_poll(). Do not put startup work in
172+
__init__(); use on_init().
173+
174+
Minimal example:
175+
class ApiPoller(PollingBusinessService):
176+
Output = target()
177+
178+
def on_poll(self):
179+
self.send_request_async(self.Output, MyRequest())
133180
134-
Declare outbound routes with target() and send messages with
135-
send_request_async(...). Do not put startup work in __init__(); use
136-
on_init(). See docs/cookbooks/add-polling-service.md.
181+
Related:
182+
docs/cookbooks/add-polling-service.md
137183
"""
138184

139185
pass
140186

141187

142188
class BusinessOperation(_BusinessOperation):
143-
"""Outbound side-effect boundary for production messages.
189+
"""Purpose:
190+
Outbound side-effect boundary for production messages.
144191
145-
Use operations for external APIs, file writes, database writes, FHIR
146-
submission, and other side effects. Dispatch can use on_message(), typed
147-
one-argument methods, or @handler(MessageType). See
148-
docs/cookbooks/add-business-operation.md.
192+
Use when:
193+
A production must call an external API, write a file, update a database,
194+
submit FHIR resources, or perform another side effect.
195+
196+
Lifecycle:
197+
IRIS calls on_message(request). IoP can dispatch to @handler methods,
198+
typed one-argument methods, or the on_message fallback.
199+
200+
Best practices:
201+
Keep external-system code here. Return a response message when callers
202+
expect synchronous results.
203+
204+
Common mistakes:
205+
Do not put routing orchestration in an operation when a BusinessProcess
206+
should own the decision.
207+
208+
Minimal example:
209+
class SubmitOrder(BusinessOperation):
210+
def on_message(self, request):
211+
return SubmitResult(ok=True)
212+
213+
Related:
214+
docs/cookbooks/add-business-operation.md
149215
"""
150216

151217
pass
152218

153219

154220
class BusinessProcess(_BusinessProcess):
155-
"""Routing, orchestration, decision, and transformation component.
221+
"""Purpose:
222+
Routing, orchestration, decision, and transformation component.
223+
224+
Use when:
225+
A production needs branching, enrichment, transformation, fan-out,
226+
request/reply orchestration, or response aggregation.
156227
157-
Declare outbound routes with target() and connect them in a Production
158-
graph. Dispatch can use on_message(), typed one-argument methods, or
159-
@handler(MessageType). See docs/cookbooks/add-business-process.md.
228+
Lifecycle:
229+
IRIS calls on_message(request). For async requests, IRIS can later call
230+
on_response(...) and on_complete(...).
231+
232+
Best practices:
233+
Declare outbound routes with target() and wire them with
234+
Production.connect(...). Use @handler(MessageType) or typed methods for
235+
multiple message types.
236+
237+
Common mistakes:
238+
Do not hard-code target component names when target() can expose a
239+
configurable route.
240+
241+
Minimal example:
242+
class Router(BusinessProcess):
243+
Accepted = target()
244+
245+
def on_message(self, request):
246+
return self.send_request_sync(self.Accepted, request)
247+
248+
Related:
249+
docs/cookbooks/add-business-process.md,
250+
docs/cookbooks/production-settings-and-targets.md
160251
"""
161252

162253
pass
@@ -175,12 +266,34 @@ class DuplexProcess(_PrivateSessionProcess):
175266

176267

177268
class Message(_Message):
178-
"""Python-only JSON-serialized message contract.
179-
180-
Use @dataclass for ordinary app messages between IoP components. Prefer
181-
PersistentMessage only when IRIS needs a native persistent message body. See
182-
docs/cookbooks/add-business-process.md and
183-
docs/cookbooks/add-business-operation.md.
269+
"""Purpose:
270+
Python-only JSON-serialized message contract.
271+
272+
Use when:
273+
IoP components exchange structured Python data and IRIS does not need a
274+
native persistent message body.
275+
276+
Lifecycle:
277+
IoP serializes dataclass fields into IOP.Message and restores the Python
278+
class on receipt.
279+
280+
Best practices:
281+
Decorate subclasses with @dataclass. Use PydanticMessage when runtime
282+
validation is more important.
283+
284+
Common mistakes:
285+
Do not use Message without @dataclass. Do not register Message classes
286+
in CLASSES; use PersistentMessage for native IRIS message bodies.
287+
288+
Minimal example:
289+
@dataclass
290+
class OrderRequest(Message):
291+
order_id: str
292+
293+
Related:
294+
docs/cookbooks/add-business-process.md,
295+
docs/cookbooks/add-business-operation.md,
296+
docs/getting-started/register-component.md
184297
"""
185298

186299
pass

src/iop/components/business_host.py

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,30 @@ def send_request_sync(
108108
timeout: int = -1,
109109
description: str | None = None,
110110
) -> Any:
111-
"""Send message synchronously to target component.
111+
"""Purpose:
112+
Send a message to a target component and wait for the response.
112113
113-
Prefer a target() attribute such as self.Output for configurable
114-
routing. BusinessProcess examples are in
115-
docs/cookbooks/add-business-process.md.
114+
Use when:
115+
The caller needs the target response before continuing.
116116
117-
Args:
118-
target: Name of target component
119-
request: Message to send
120-
timeout: Timeout in seconds, -1 means wait forever
121-
description: Optional description for logging
122-
123-
Returns:
124-
Response from target component
125-
126-
Raises:
127-
TypeError: If request is invalid type
117+
Lifecycle:
118+
IoP serializes request, calls the IRIS synchronous dispatch API, and
119+
deserializes the response before returning.
120+
121+
Best practices:
122+
Pass a target() attribute such as self.Output so the route is
123+
configurable in the Production graph.
124+
125+
Common mistakes:
126+
Do not use synchronous calls for long-running work unless the caller
127+
really must block.
128+
129+
Minimal example:
130+
response = self.send_request_sync(self.Output, request)
131+
132+
Related:
133+
docs/cookbooks/add-business-process.md,
134+
docs/cookbooks/production-settings-and-targets.md
128135
"""
129136
target = resolve_target(target)
130137
return self.iris_handle.dispatchSendRequestSync(
@@ -138,19 +145,29 @@ def send_request_async(
138145
request: Message | Any,
139146
description: str | None = None,
140147
) -> None:
141-
"""Send message asynchronously to target component.
148+
"""Purpose:
149+
Send a message to a target component without waiting for a response.
142150
143-
Prefer a target() attribute such as self.Output for configurable
144-
routing. Polling service examples are in
145-
docs/cookbooks/add-polling-service.md.
151+
Use when:
152+
A service or operation should enqueue downstream work and continue.
146153
147-
Args:
148-
target: Name of target component
149-
request: Message to send
150-
description: Optional description for logging
151-
152-
Raises:
153-
TypeError: If request is invalid type
154+
Lifecycle:
155+
IoP serializes request and calls the IRIS asynchronous dispatch API.
156+
157+
Best practices:
158+
Pass a target() attribute such as self.Output so the route is
159+
configurable in the Production graph.
160+
161+
Common mistakes:
162+
Do not use this helper when the caller requires a response; use
163+
send_request_sync(...) or the BusinessProcess async response flow.
164+
165+
Minimal example:
166+
self.send_request_async(self.Output, request)
167+
168+
Related:
169+
docs/cookbooks/add-polling-service.md,
170+
docs/cookbooks/production-settings-and-targets.md
154171
"""
155172
target = resolve_target(target)
156173
return self.iris_handle.dispatchSendRequestAsync(target, request, description)

src/iop/components/business_operation.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,31 @@ class _BusinessOperation(_BusinessHost):
1919
adapter: Any = None
2020

2121
def on_message(self, request: Any) -> Any:
22-
"""Handle incoming messages.
23-
24-
Process messages received from other production components and either
25-
send to external system or forward to another component.
26-
27-
Args:
28-
request: The incoming message
29-
30-
Returns:
31-
Response message
22+
"""Purpose:
23+
Handle an incoming message sent to a BusinessOperation.
24+
25+
Use when:
26+
The operation must perform an outbound side effect or submit data to
27+
an external system.
28+
29+
Lifecycle:
30+
IRIS invokes this hook for operation requests unless dispatch routes
31+
the message to a @handler or typed one-argument method first.
32+
33+
Best practices:
34+
Keep side effects isolated here. Return a response message when the
35+
caller uses send_request_sync(...).
36+
37+
Common mistakes:
38+
Do not put routing decisions here when a BusinessProcess should
39+
orchestrate them.
40+
41+
Minimal example:
42+
def on_message(self, request):
43+
return SubmitResult(ok=True)
44+
45+
Related:
46+
docs/cookbooks/add-business-operation.md
3247
"""
3348
warnings.warn(
3449
f"{self.__class__.__name__} did not override on_message(); "

0 commit comments

Comments
 (0)