# Abstracting your services
One of the many advantages of using a dependency injection library is that you can actually abstract your services from their real implementation. For example, let's take a cache service:
interface ICache {
set(key: string, value: any): void;
get(key: string): any;
}
We could have multiple class implementing this interface.
class RedisCache implements ICache { /* ... */ }
class LocalCache implements ICache { /* ... */ }
But how should we reference it ?
class MyService {
// Using an union type
private cache: RedisCache | LocalCache;
// Using the interface
private cache: ICache;
}
But which implementation should we inject into MyService
?
# Identifying your services
When you declare a service with the @Service
decorator, Diosaur
automatically assigns a service identifier to your service class. By default,
it's the actual service class which is convenient to register unique
services. That's also why when you use @Inject()
without identifier, Diosaur
is able to retrieve the correct service by checking the attribute type.
But in the case where you have multiple implementations to answer a single need, you must provide an identifier that matches all of them.
Taking the previous example, we cannot use an interface because it's an element which will be lost at runtime. Diosaur allows you to use constructors, strings and symbols as service identifiers.
Our example could then become:
const TYPES = {
cache: Symbol('cache')
};
@Service({ identifier: TYPES.cache })
class RedisCache implements ICache { /* ... */ }
@Service({ identifier: TYPES.cache })
class LocalCache implements ICache { /* ... */ }
# Tagging your services
The previous step allowed us to declare multiple implementations with a unique identifier, but we must now have a way to uniquely identify each of them. That's where tagging comes in handy.
Diosaur allows you to tag your services easily.
const TYPES = {
cache: Symbol('cache')
};
@Service({ identifier: TYPES.cache, tag: 'redis' })
class RedisCache implements ICache { /* ... */ }
@Service({ identifier: TYPES.cache, tag: 'local' })
class LocalCache implements ICache { /* ... */ }
Tags in Diosaur are typed as string | null
, if you don't provide a tag it's tagged
as null
which means the default implementation for this identifier.
Now we can reference our cache implementation in our service:
@Service()
class MyService {
@Service({ identifier: TYPES.cache, tag: 'redis' })
private cache: ICache;
}
But typing it using the interface. As such we decoupled the implementation from the usage completely.
# Manually retrieving a tagged service
You can retrieve tagged services easily by doing
import { getContainer} from 'diosaur';
const container = await getContainer();
container.get(TYPES.cache, 'redis'); // Instance of RedisCache
# Parameter as tag
Diosaur also allows you to define tags as parameters to push things even further.
setParameter('cache', 'redis');
@Service()
class MyService {
@Service({ identifier: TYPES.cache, tag: '@cache' }) // resolves to redis
private cache: ICache;
}
// ...
const container = await getContainer();
container.get(TYPES.cache, '@cache'); // Instance of RedisCache