Skip to content

Commit e3c3c4e

Browse files
committed
Update agent context.
1 parent 9d8f3f5 commit e3c3c4e

File tree

3 files changed

+827
-0
lines changed

3 files changed

+827
-0
lines changed

context/best-practices.md

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# Best Practices
2+
3+
This guide outlines recommended patterns and practices for building robust, maintainable services with `async-service`.
4+
5+
## Application Structure
6+
7+
If you are creating an application that runs services, you should define a top level `services.rb` file:
8+
9+
### Service Configuration
10+
11+
Create a single top-level `service.rb` file as your main entry point:
12+
13+
```ruby
14+
#!/usr/bin/env async-service
15+
16+
# Load your service configurations
17+
require_relative 'lib/my_library/environment/web_environment'
18+
require_relative 'lib/my_library/environment/worker_environment'
19+
20+
service "web" do
21+
include MyLibrary::Environment::WebEnvironment
22+
end
23+
24+
service "worker" do
25+
include MyLibrary::Environment::WorkerEnvironment
26+
end
27+
```
28+
29+
### Multiple Service Configurations
30+
31+
In some cases, you may want to define multiple service configurations, e.g. for different environments or deployment targets. In those cases, you may create `web_service.rb` or `job_service.rb`, but this usage should be discouraged.
32+
33+
## Library Structure
34+
35+
If you are creating a library that exposes services, use the following structure and guidelines:
36+
37+
### Directory Structure
38+
39+
Organize your code following these conventions:
40+
41+
```
42+
├── service.rb
43+
└── lib/
44+
└── my_library/
45+
├── environment/
46+
│ ├── web_environment.rb
47+
│ ├── worker_environment.rb
48+
│ ├── database_environment.rb
49+
│ └── tls_environment.rb
50+
└── service/
51+
├── web_service.rb
52+
└── worker_service.rb
53+
```
54+
55+
### Environment Organization
56+
57+
Place environments in `lib/my_library/environment/`:
58+
59+
```ruby
60+
# lib/my_library/environment/web_environment.rb
61+
module MyLibrary
62+
module Environment
63+
module WebEnvironment
64+
include Async::Service::ContainerEnvironment
65+
66+
def service_class
67+
MyLibrary::Service::WebService
68+
end
69+
70+
def port
71+
3000
72+
end
73+
74+
def host
75+
"localhost"
76+
end
77+
end
78+
end
79+
end
80+
```
81+
82+
### Service Organization
83+
84+
Place services in `lib/my_library/service/`:
85+
86+
```ruby
87+
# lib/my_library/service/web_service.rb
88+
module MyLibrary
89+
module Service
90+
class WebService < Async::Service::ContainerService
91+
private def format_title(evaluator, server)
92+
if server&.respond_to?(:connection_count)
93+
"#{self.name} [#{evaluator.host}:#{evaluator.port}] (#{server.connection_count} connections)"
94+
else
95+
"#{self.name} [#{evaluator.host}:#{evaluator.port}]"
96+
end
97+
end
98+
99+
def run(instance, evaluator)
100+
# Start your service and return the server object.
101+
# ContainerService handles container setup, health checking, and process titles.
102+
start_web_server(evaluator.host, evaluator.port)
103+
end
104+
105+
private
106+
107+
def start_web_server(host, port)
108+
# The return value of this method will be the server object which is returned from `run` and passed to `format_title`.
109+
end
110+
end
111+
end
112+
end
113+
```
114+
115+
### Use `ContainerEnvironment` for Services
116+
117+
Include {ruby Async::Service::ContainerEnvironment} for services that run in containers using {ruby Async::Service::ContainerService}:
118+
119+
```ruby
120+
module WebEnvironment
121+
include Async::Service::ContainerEnvironment
122+
123+
def service_class
124+
WebService
125+
end
126+
end
127+
```
128+
129+
## Environment Best Practices
130+
131+
### Use Plain Modules
132+
133+
Prefer plain Ruby modules for environments:
134+
135+
```ruby
136+
module DatabaseEnvironment
137+
def database_url
138+
"postgresql://localhost/app"
139+
end
140+
141+
def max_connections
142+
10
143+
end
144+
end
145+
```
146+
147+
### One-to-One Service-Environment Correspondence
148+
149+
Maintain a 1:1 relationship between services and their primary environments:
150+
151+
```ruby
152+
# Primary environment for WebService
153+
module WebEnvironment
154+
def service_class
155+
WebService
156+
end
157+
158+
# Default configuration:
159+
def port
160+
3000
161+
end
162+
163+
def host
164+
'0.0.0.0'
165+
end
166+
end
167+
168+
# Primary environment for WorkerService
169+
module WorkerEnvironment
170+
def service_class
171+
WorkerService
172+
end
173+
174+
def queue_name
175+
'default'
176+
end
177+
178+
def count
179+
4
180+
end
181+
end
182+
```
183+
184+
### Compose with Auxiliary Environments
185+
186+
Use additional environments for cross-cutting concerns:
187+
188+
```ruby
189+
module WebEnvironment
190+
include DatabaseEnvironment
191+
include TLSEnvironment
192+
include LoggingEnvironment
193+
194+
def service_class
195+
WebService
196+
end
197+
end
198+
```
199+
200+
## Service Best Practices
201+
202+
### Use ContainerService as Base Class
203+
204+
Prefer `Async::Service::ContainerService` over `Generic` for most services:
205+
206+
```ruby
207+
class WebService < Async::Service::ContainerService
208+
# ContainerService automatically handles:
209+
# - Container setup with proper options.
210+
# - Health checking with process title updates.
211+
# - Integration with Formatting module.
212+
213+
private def format_title(evaluator, server)
214+
# Customize process title display
215+
"#{self.name} [#{evaluator.host}:#{evaluator.port}]"
216+
end
217+
218+
def run(instance, evaluator)
219+
# Focus only on your service logic
220+
start_web_server(evaluator.host, evaluator.port)
221+
end
222+
end
223+
```
224+
225+
### Implement Meaningful Process Titles
226+
227+
Use the `format_title` method to provide dynamic process information:
228+
229+
```ruby
230+
private def format_title(evaluator, server)
231+
# Good - Include service-specific info
232+
"#{self.name} [#{evaluator.host}:#{evaluator.port}]"
233+
234+
# Better - Include dynamic runtime status
235+
if connection_count = server&.connection_count
236+
"#{self.name} [#{evaluator.host}:#{evaluator.port}] (C=#{format_count connection_count})"
237+
else
238+
"#{self.name} [#{evaluator.host}:#{evaluator.port}]"
239+
end
240+
end
241+
```
242+
243+
Try to keep process titles short and focused.
244+
245+
### Use `start` and `stop` Hooks for Shared Resources
246+
247+
Utilize the `start` and `stop` hooks to manage shared resources effectively:
248+
249+
```ruby
250+
class WebService < Async::Service::ContainerService
251+
def start
252+
# Bind to the endpoint in the container:
253+
@endpoint = @evaluator.endpoint.bind
254+
255+
super
256+
end
257+
258+
def stop
259+
@endpoint&.close
260+
end
261+
end
262+
```
263+
264+
## Testing Best Practices
265+
266+
### Test Environments in Isolation
267+
268+
Test environment modules independently:
269+
270+
```ruby
271+
# test/my_library/environment/web_environment.rb
272+
describe MyLibrary::Environment::WebEnvironment do
273+
let(:environment) do
274+
Async::Service::Environment.build do
275+
include MyLibrary::Environment::WebEnvironment
276+
end
277+
end
278+
279+
it "provides default port" do
280+
expect(environment.port).to be == 3000
281+
end
282+
end
283+
```
284+
285+
### Test Services with Service Controller
286+
287+
Use test environments for service testing:
288+
289+
```ruby
290+
# test/my_library/service/web_service.rb
291+
describe MyLibrary::Service::WebService do
292+
let(:environment) do
293+
Async::Service::Environment.build do
294+
include MyLibrary::Environment::WebEnvironment
295+
end
296+
end
297+
298+
let(:evaluator) {environment.evaluator}
299+
let(:service) {evaluator.service_class(environment, evaluator)}
300+
let(:controller) {Async::Service::Controller.for(service)}
301+
302+
before do
303+
controller.start
304+
end
305+
306+
after do
307+
controller.stop
308+
end
309+
310+
let(:uri) {URI "http://#{evaluator.host}:#{evaluator.port}"}
311+
312+
it "responds to requests" do
313+
Net::HTTP.get(uri).tap do |response|
314+
expect(response).to be_a(Net::HTTPSuccess)
315+
end
316+
end
317+
end
318+
```
319+
320+
Note that full end-to-end service tests like this are typically slow and hard to isolate, so it's better to use unit tests for individual components whenever possible.

context/index.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,11 @@ files:
1010
title: Getting Started
1111
description: This guide explains how to get started with `async-service` to create
1212
and run services in Ruby.
13+
- path: service-architecture.md
14+
title: Service Architecture
15+
description: This guide explains the key architectural components of `async-service`
16+
and how they work together to provide a clean separation of concerns.
17+
- path: best-practices.md
18+
title: Best Practices
19+
description: This guide outlines recommended patterns and practices for building
20+
robust, maintainable services with `async-service`.

0 commit comments

Comments
 (0)