Skip to content

Commit fa6e7ba

Browse files
committed
Improve documentation
1 parent d38d628 commit fa6e7ba

File tree

2 files changed

+223
-284
lines changed

2 files changed

+223
-284
lines changed

docs/guide/mortymer-dependency-injection.md

Lines changed: 50 additions & 284 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ When you include `Mortymer::DependenciesDsl`, you get access to the `inject` dir
2727
1. Create an instance variable `@user_repository`
2828
2. Automatically initialize it with an instance of `UserRepository`
2929

30+
The name of the injected variable is the snake_case version of the class we are injecting by default. So
31+
for example `Repositories::UserRepository` will result in an instance variable called `@user_repository`
32+
3033
### Custom Variable Names
3134

3235
You can customize the instance variable name using the `as` option:
@@ -66,54 +69,61 @@ end
6669

6770
## Advanced Usage
6871

69-
### Dependency Scopes
70-
71-
Mortymer supports different scopes for dependencies:
72-
73-
#### Singleton Scope (Default)
74-
75-
By default, dependencies are singleton-scoped, meaning the same instance is shared across the application:
72+
Just with the basic information, Mortymer already provides a really powerful and
73+
expressive system to declare dependencies, but as it is, is just a Fancy Factory or Initializer
74+
for your classes. Where Dependency Injection really shines, is when you are able to control some
75+
other aspects of the dependency injection cycle. For example, Mortymer does not interfere with your
76+
usual object initialization.
7677

7778
```ruby
78-
class UserService
79+
class MyService
7980
include Mortymer::DependenciesDsl
81+
inject Mailer
8082

81-
inject UserRepository # Singleton by default
82-
end
83+
def initialize(user)
84+
# At this point we can be sure that the @mailer instance
85+
# is already defined
86+
@mailer.send_email("Hello user", user.email)
87+
@user = user
88+
end
8389
```
8490

85-
#### Request Scope
91+
It might not be clear what is happening here. What is really doing Mortymer is
92+
wrapping the `#initialize` method with the dependency resolving algorithm and if
93+
any `kwarg` passed to the `#initialize` method matches the name of the injected
94+
dependency, then Mortymer will use that as the dependency instead.
8695

87-
For dependencies that should be unique per request:
96+
This means that you can call your previous service as:
8897

8998
```ruby
90-
class UserService
91-
include Mortymer::DependenciesDsl
92-
93-
inject UserRepository, scope: :request
94-
end
99+
MyService.new(User.first) # will use the Mailer class to instantiate the @mailer variable
100+
MyService.new(User.first, mailer: instance_double(Mailer)) # will override @mailer with an instance_double
95101
```
96102

97-
#### Transient Scope
103+
### Dependency Scopes
98104

99-
For dependencies that should be newly instantiated every time:
105+
Mortymer supports different scopes for dependencies:
100106

101-
```ruby
102-
class UserService
103-
include Mortymer::DependenciesDsl
107+
- By default, dependencies are transient, meaning you will get a new fresh instance each time you ask for one
108+
- Dependencies might be singleton, meaning each injection will provide the same instance
109+
- Dependencies might be lazy, basically, a block that might compute a value for your dependency
104110

105-
inject UserRepository, scope: :transient
106-
end
107-
```
111+
To change a dependency's scope, you need to register it in the Mortymer DI Container
108112

109-
### Factory Registration
113+
#### Singleton Scope
110114

111-
You can register custom factory methods for your dependencies:
115+
```ruby
116+
Mortymer.container.register_constant(Mailer, SingletonMailer.new)
117+
```
118+
119+
#### Lazy Scope
112120

113121
```ruby
114-
Mortymer.configure do |config|
115-
config.register_factory(UserRepository) do
116-
UserRepository.new(connection: DatabaseConnection.current)
122+
Mortymer.container.register_constant(MAILER_ADMIN_ADDRESS) do
123+
if some_condition?
124+
"admin+#{Time.current}@example.com"
125+
else
126+
"admin-ex@example.com"
117127
end
118128
end
119129
```
@@ -129,6 +139,12 @@ module UserRepositoryInterface
129139
end
130140
end
131141

142+
class UserService
143+
include Mortymer::DependenciesDsl
144+
145+
inject UserRepositoryInterface, as: :repo
146+
end
147+
132148
class PostgresUserRepository
133149
include UserRepositoryInterface
134150
# implementation
@@ -140,259 +156,9 @@ class MongoUserRepository
140156
end
141157

142158
# Register implementation
143-
Mortymer.configure do |config|
144-
config.register_implementation(UserRepositoryInterface, PostgresUserRepository)
145-
end
146-
147-
class UserService
148-
include Mortymer::DependenciesDsl
149-
150-
inject UserRepositoryInterface
151-
end
152-
```
153-
154-
## Testing
155-
156-
### Mocking Dependencies
157-
158-
Mortymer makes it easy to mock dependencies in tests:
159-
160-
```ruby
161-
RSpec.describe UserService do
162-
let(:repository_mock) { instance_double(UserRepository) }
163-
let(:mailer_mock) { instance_double(UserMailer) }
164-
165-
before do
166-
Mortymer.stub_dependency(UserRepository, repository_mock)
167-
Mortymer.stub_dependency(UserMailer, mailer_mock)
168-
end
169-
170-
it "creates a user" do
171-
service = UserService.new
172-
expect(repository_mock).to receive(:create)
173-
expect(mailer_mock).to receive(:send_welcome_email)
174-
175-
service.create_user(name: "John")
176-
end
177-
end
178-
```
179-
180-
### Test-Specific Implementations
181-
182-
You can also register test-specific implementations:
183-
184-
```ruby
185-
class TestUserRepository
186-
include UserRepositoryInterface
187-
188-
def initialize
189-
@users = {}
190-
end
191-
192-
def find(id)
193-
@users[id]
194-
end
195-
end
196-
197-
RSpec.describe UserService do
198-
before do
199-
Mortymer.register_implementation(UserRepositoryInterface, TestUserRepository)
200-
end
201-
end
202-
```
203-
204-
## Best Practices
205-
206-
### 1. Keep Dependencies Explicit
207-
208-
Always use explicit constant references rather than strings for dependencies:
209-
210-
```ruby
211-
# Good
212-
inject UserRepository
213-
214-
# Avoid
215-
inject "user_repository"
216-
```
217-
218-
### 2. Use Meaningful Names
219-
220-
Choose descriptive names for your dependencies:
221-
222-
```ruby
223-
# Good
224-
inject UserRepository, as: :active_users_repository
225-
226-
# Less Clear
227-
inject UserRepository, as: :repo
228-
```
229-
230-
### 3. Group Related Dependencies
231-
232-
Keep related dependencies together and organize them logically:
233-
234-
```ruby
235-
class UserService
236-
include Mortymer::DependenciesDsl
237-
238-
# Authentication dependencies
239-
inject AuthenticationService
240-
inject TokenGenerator
241-
242-
# User management dependencies
243-
inject UserRepository
244-
inject UserMailer
159+
Mortymer.container.register_constant(UserRepositoryInterface, PostgresUserRepository.new)
245160

246-
# Logging/Monitoring
247-
inject Logger
248-
inject MetricsCollector
249-
end
250-
```
251-
252-
### 4. Interface Segregation
253-
254-
Create focused interfaces for your dependencies:
255-
256-
```ruby
257-
module UserReader
258-
def find(id); end
259-
def list; end
260-
end
261-
262-
module UserWriter
263-
def create(attributes); end
264-
def update(id, attributes); end
265-
end
266-
267-
class UserRepository
268-
include UserReader
269-
include UserWriter
270-
end
271-
272-
class UserService
273-
include Mortymer::DependenciesDsl
274-
275-
inject UserReader # Only inject what you need
276-
end
277-
```
278-
279-
## Common Patterns
280-
281-
### Service Objects
282-
283-
```ruby
284-
class CreateUser
285-
include Mortymer::DependenciesDsl
286-
287-
inject UserRepository
288-
inject UserMailer
289-
inject UserValidator
290-
291-
def call(params)
292-
@user_validator.validate!(params)
293-
user = @user_repository.create(params)
294-
@user_mailer.send_welcome_email(user)
295-
user
296-
end
297-
end
298-
```
299-
300-
### Decorators
301-
302-
```ruby
303-
class LoggedUserRepository
304-
include Mortymer::DependenciesDsl
305-
306-
inject UserRepository
307-
inject Logger
308-
309-
def find(id)
310-
@logger.info("Finding user: #{id}")
311-
result = @user_repository.find(id)
312-
@logger.info("Found user: #{result&.id}")
313-
result
314-
end
315-
end
316-
```
317-
318-
## Configuration
319-
320-
### Global Configuration
321-
322-
```ruby
323-
Mortymer.configure do |config|
324-
# Register default implementations
325-
config.register_implementation(UserRepositoryInterface, PostgresUserRepository)
326-
327-
# Register factories
328-
config.register_factory(Logger) do
329-
Logger.new($stdout).tap do |logger|
330-
logger.level = Rails.env.production? ? :info : :debug
331-
end
332-
end
333-
334-
# Configure scopes
335-
config.set_scope(UserSession, :request)
336-
end
337-
```
338-
339-
### Environment-Specific Configuration
340-
341-
```ruby
342-
# config/initializers/mortymer.rb
343-
Mortymer.configure do |config|
344-
if Rails.env.test?
345-
config.register_implementation(UserRepositoryInterface, TestUserRepository)
346-
elsif Rails.env.development?
347-
config.register_implementation(UserRepositoryInterface, DevUserRepository)
348-
else
349-
config.register_implementation(UserRepositoryInterface, ProductionUserRepository)
350-
end
351-
end
161+
# Then you can instantiate
162+
UserService.new # will use the PostgresUserRepository implementation
163+
UserService.new(repo: MongoUserRepository.new) # override the dependency to use a different one
352164
```
353-
354-
## Troubleshooting
355-
356-
### Common Issues
357-
358-
1. **Circular Dependencies**
359-
360-
```ruby
361-
# This will raise a CircularDependencyError
362-
class UserService
363-
include Mortymer::DependenciesDsl
364-
inject AccountService
365-
end
366-
367-
class AccountService
368-
include Mortymer::DependenciesDsl
369-
inject UserService # Circular dependency!
370-
end
371-
```
372-
373-
Solution: Refactor to remove the circular dependency or use method injection.
374-
375-
2. **Missing Dependencies**
376-
377-
```ruby
378-
# This will raise a DependencyNotFoundError
379-
class UserService
380-
include Mortymer::DependenciesDsl
381-
inject NonExistentService
382-
end
383-
```
384-
385-
Solution: Ensure all dependencies are properly registered or the constants exist.
386-
387-
3. **Scope Conflicts**
388-
389-
```ruby
390-
# This might cause issues
391-
class UserService
392-
include Mortymer::DependenciesDsl
393-
inject SessionManager, scope: :request # Request-scoped
394-
inject UserRepository # Singleton-scoped
395-
end
396-
```
397-
398-
Solution: Be careful mixing different scopes and ensure it makes sense for your use case.

0 commit comments

Comments
 (0)