How can I access a repository globally in loopback 4? - node.js

I used loopback 4 to bootstrap my API application and developed some parts of it.
Now I'm trying to access repositories outside the controllers and tried everything I could but didn't help. None of my searches did too. For example I have a controller in which I can access repo such way.
constructor(
#repository(UserRepository) public userRepository: UserRepository){}
But if it isn't a controller it won't work and I found out I had a wrong understanding about #repository and #inject decorators.
My Case:
I want to run a cron-job to perform an update operation on database every day at a specific time.
The thing is I think I should create a service or something like that to expose database operations so it can be accessible anywhere.

The issue you're trying to tackle comes down to dependency injection. It's described at https://loopback.io/doc/en/lb4/Dependency-injection.html but that article does not explain how to create a separate class that works with dependency injection.
In short, in order to perform the dependency injection through decorators, LoopBack needs to be the one to construct the class you're writing, so that it can pick what you need from its Context. So, instead of writing new ClassSomething, it has to be #inject(ClassSomething) or #service(ClassSomething), and those decorators only work in a class also instantiated by LoopBack. In other words, your class has to be in the "call tree" from the original application class (application.ts) for dependency injection to work.
The easiest way to do what you want to do is to use LoopBack's Cron component: see https://loopback.io/doc/en/lb4/Running-cron-jobs.html. You could convert your class to a CronJob if it has no other purpose, or let the CronJob create an instance of your class. In the latter case, you need to bind your class to the application Context so that the CronJob can access it through decorators. You can do so in two ways:
Convert your class to a Service (see https://loopback.io/doc/en/lb4/Service.html and https://loopback.io/doc/en/lb4/Service-generator.html). If what your class does can be thought of as a service, this is the way to go.
Inject your class with: #service(MyServiceClass) myService: MyServiceClass
Directly bind your class to the application context, in application.ts:
this.bind('MyClass').toClass(MyClass);
Inject your class with: #inject(MyClass) myClass: MyClass
The latter option is not best practice AFAIU as it does not adhere to IoC-principles (https://en.wikipedia.org/wiki/Inversion_of_control) - basically, by hard-coding the class binding in the application class, it is not exactly modular. When your class is converted to a service (and placed in src/services), it will automatically be added to the Context, meaning you can inject it everywhere using #service().

I also experience the same(cron word). I couldn't find any solution still in the documentation. But still, I have achieved this in this way.
just call repository as like class with dependency in it. do this in your index.ts file where you start the application.
async function startCronJobs(app: SawLoopbackApplication) {
const userRepo = app.repository(UserRepository);
const userRepoInstance = await userRepo.getValue(app);
const cron = new CronComponent(userInstance);
//---- Exicute your cron here
new CronJob('0 6 * * *', function () {
cron.sendMorningMail()
}).start();
//---- Exicute your Second Cron
new CronJob('0 8 * * 1', function () {
cron.weeklyAppraisalsToAgent()
}).start();
}
call your cron in the functional component and execute a raw query to get the details from DB etc...
import {MysqlDataSource} from '../../datasources'
const query = `select * from accounts where IP="${ipAddress}" ORDER By
createdAt DESC Limit 1`
const dataSource = new MysqlDataSource()
const accountDetails = await dataSource.execute(query)
console.log(accountDetails)
instead of a raw query. call your repository from the function component below
import {MysqlDataSource} from '../datasources'
import {ContactUsUserDetailsRepository} from '../repositories'
const contactUsUserDetailsRepository = new ContactUsUserDetailsRepository(new MysqlDataSource)
function saveContactDetails(){
const payload={UID:"Unique ID",name:"Durai"}
await contactUsUserDetailsRepository.create(payload)
}
I would prefer 2 because, if you have a lot of imports in your cron class constractor you need to pass all of them while calling a function like in the 3rd point.
Have fun:)

Related

NestJS + MikroORM EntityManager Validation Error

I started migrating a project from typeorm to MikroORM and I've been facing an issue when calling the same ( or different ) repository more than once. The error is as follows:
ValidationError: Using global EntityManager instance methods for context specific actions is disallowed. If you need to work with the global instance's identity map, use 'allowGlobalContext' configuration option or 'fork()' instead.
A more detailed explanation is:
I am using NestJS
DB is postgreSQL
GRPC communication for this specific service ( not sure if that matters )
Wrapping repository calls in rxjs observables: e.g. from(this.userRepository.findOne({id: user.id})
Now, I've read through the documentation and other issues here and on github mainly regarding the EntityManager and the RequestContext. I've added the #UseRequestContext() annotation on my controller ( and injected the private readonly orm: MikroORM, instance in the constructor ):
#GrpcMethod('UsersService', 'Login')
#UseRequestContext()
login(user: UserLogin): Observable<UserResponse> {
return this.userService.login(user);
}
The service itself calls userRepository.find... and via debug I can see the executed query and the result is fetched, however I have another call to the roleRepository later on down the line. This call fails with the error above. I've played around with it and it seems like even if I try to call the same userRepoistory.find... for a second time it will fail again: e.g.:
loginUser(user: UserLogin) {
return this.getUserById(user.id).pipe(
switchMap(() => this.getUserById(user.id)),
);
}
If I do the following inside the getUserById method however, it works like a charm:
getUserById(id: string): Observable < User > {
const emFork = this.em.fork();
return from(emFork.findOne(User, {
userId: id
}));
Basically what I am trying to understand is:
It says in the documentation that the function marked with #UseRequestContext() should not return anything, however when placing it on the Grpc method it works ( at least for the first repository call ). If I remove it I get the same error before calling any repositories. Am I using it correctly?
If the forking of the EntityManager is the correct approach, wouldn't that make creating Repository classes and injecting them obsolete? Also isn't forking it each time going create a lot of instances?
It's obvious I am lacking understanding on how this context works, so an explanation would be greatly appreciated!
Also, any other hints/tips are welcome!
Thank you for your time :)

posibilty to get service whithout moduleRef.get

I Can take instance of service using this example:
export class GetEntityDomainService {
constructor(private readonly moduleRef: ModuleRef) { }
getEntity(): void {
const myObject = this.moduleRef.get(MyClassName);
}
}
but how could I invoke a service instance outside of a class object
where I don't have a handle to moduleRef:
here is example from angular:
const injector = ReflectiveInjector.resolveAndCreate(providers);
const widgets: WidgetService = injector.get(WidgetService);
https://kevinphelps.me/blog/2017-01-17-using-angular-dependency-injection-in-node
whether is it possible to download an instance of the service without needing a moduleRef?
Thanks
Piotr
There are several reasons
sometimes I need to inject a service in a function, then I have to pass moduleRef to this function to make it possible
The second example is Command and Events -
for example I create such an action, then I pass parameters to send to handler,:
this.dispach(new Command (parameters ...))
Currently my actions know from which module they are triggered and in which module their handler is - so I won't send actions between wrong areas of the system (layers, dependencies). I check all this when executing
this.dispach (new Command ())
now I have to write such a dispatch as follows:
this.dispach (new Command (this.moduleRef, ... other parameters))
could I create a moduleRef from the code?, it would facilitate the process of creating an action and eliminate the need to pass moduleRef
but I don't know if it will help me
as I analyzed DI nest, it works differently from angular
The service knows where it was declared and when it injects something in it, it uses the tokens declared in this module
I suppose I will only be able to inject general scope

Abstracting class using mongoose

I'm developing an application in which I need to have some abstraction.
I mean there, that I would like to "simulate" an interface behaviour like creating a contract inside of my concrete classes.
Actually, dealing with Users, I'm having a UserMongoRepository class with the contract implemented :
getAll() returns the full list of users by promise
getById(id) returns the user concerned by promise
save(user) saves the user by promise
... etc
I have the same methods implemented inside of the UserMysqlRepository (allowing me to switch behaviour when a change is needed.
Problem
My problem is that I'm dealing with Mongoose that doesn't act like a datamapper, but more like an active record.
It means that my implementation of save(user) would be a bit weird like following :
save(user){
let mongooseUser = this.convert(user);
return user.save();
}
The convert method allows me to switch from a standard Model to a specific Mongoose model. It allows me, again, to have some abstraction and to don't have to rewrite my full application data access.
My real problem is when I try to unit test my full class :
import MongooseUser from '../../auth/mongooseModel/MongooseUser';
/**
* UserMongoRepositoryclass
*/
export default class UserMongoRepository{
/**
* Create an UserMongoRepository
*/
constructor(){
}
/**
* Convert a User to a MongooseUser
*/
convert(user){
return new MongooseUser({email:user.mail,password:user.password,firstname:user.firstName, lastname:user.lastName});
}
findById(id){
return MongooseUser.find({id:id});
}
save(user){
return user.save();
}
}
In a standard way, I would inject my DAO inside of my constructor, and being able to mock it.
In the case of mongoose, it's a bit disturbing, because the element that makes the job isn't an instantiated object (so that I can mock it) but a class definition imported at the top of the document.
Solutions
Should I pass the MongooseUser class definition as a parameter inside of the constructor ?
Implying that I will have this code inside of the convert method :
let user = new this.MongooseUser({})
Have you got a better idea, to abstract mongoose behaviour in data mapper way ?
I don't want to use another module, it's, in my sense, the most advanced one with NodeJS...
I'm not familiar with the import syntax (nor EMCASCRIPT-6), though you say you're using node.js, so I'd recommend using the proxquire package. The idea is that the package allows you to require an external package, while stubbing the requirements that that package would use. So in your case, you could do something like:
proxyquire('../my/class/that/uses/mongoose', {
mongoose: MyTestMongooseImplementation
})
Which would allow you to use your own mongoose implementation while still using your MongooseUser as you have defined it in your package. Alternatively, you could just override the the MongooseUser class (path relative to the the file whose requirements you are stubbing:
proxyquire('/path/to/UserMongooseRepository', {
'../../auth/mongooseModel/MongooseUser': MyTestMongooseUser
})
Documentation: https://www.npmjs.com/package/proxyquire

using components in Cakephp 2+ Shell

I am trying to implement a task using the cakephp shell for my application. The task involves running a long running process (hence the need to use the shell).
The function requires me to use a function inside a Component called CommonComponent
Unfortunately whenever i try to include the component i get the following error
PHP Fatal error: Class 'Component' not found in /var/www/nginx-test/app/Controller/Component/CommonComponent.php
Here is the CronShell Class which is being called
class CronShell extends AppShell {
public function main() {
$this->out('Hello world.');
// $this->out(phpinfo());
}
public function test()
{
$this->out('Before Import');
App::import('Component', 'Common');
$this->out('Import complete');
// $this->Common=ClassRegistry::init('CommonComponent');
$this->Common =new CommonComponent();
$this->out('Initialization complete');
$this->Common->testCron();
$this->out('FunctionCall complete');
//$this->Common->saveCacheEntry("name","value");
}
}
The CommonComponent class is stored as app/Controller/Component/CommonComponent.php and is as follows
class CommonComponent extends Component
{
function testCron()
{
$this->out('Hello world from Component.');
}
}
Any ideas?
I had to do this recently with MTurk component I wrote. My final solution was using a lib instead of a component. Then I had the component access the lib so I could use the methods from both a component and from shell.
However, here is code that WILL allow you to load a component from a shell.
<?php
App::uses('AppShell', 'Console/Command');
App::uses('ComponentCollection', 'Controller');
App::uses('Controller', 'Controller');
App::uses('MTurkComponent', 'Controller/Component');
class ProcessCompletedTask extends Shell {
public function execute() {
$this->out("Processing...\n");
$collection = new ComponentCollection();
$this->MTurk = new MTurkComponent($collection);
$controller = new Controller();
$this->MTurk->initialize($controller);
$this->MTurk->processHITs();
$this->out("Complete\n");
}
}
What you import into the Shell should be code from within your Apps Lib
the component can also make use of the Lib code - but you'll not need to do a load of tedious stuff
if you set it up right you'll make you app cleaner
if you import the component you'll need to pass it a component collection and so you'd have to make that from witin shell not that your use it (or if you do you must be doing it wrong)
Have you tried App::uses('Component', 'Controller'); at the top of your file, ev. before import of CommonComponent? Then I guess you need to do what sam says, or you could use the $this->Controller->Components->load('CommonComponent') but for that you need to construct the Controller class.
I believe that it is semantically wrong to share functionality between controllers and shells.
If you require common functionality, it is clearer and neater to put it in a separate class, place this class in your vendors folder and then import the code into both the controller and the shell.
In addition, this approach does not prevent you from creating components and tasks that use the core functionality and then share these components and tasks between your controllers and shells.

MEF and Factory Pattern

i am trying to refactor my project to improve testability, therefor i'm introducing an abstract factory.
My application collects data from different sources by using ICrawlers.
These ICrawlers use 3rd party libraries to access different sources, like e.g. twitter.
Example: My TwitterCrawler uses TweetSharp to access twitter data.
My first version strongly coupled the TweetSharp client to the Crawler. Now i abstracted the TweetSharp to a ITwitterClient and a TweetSharpTwitterClient implementation.
Next step is to introduce a ITwitterClientFactory with a DefaultTwitterClientFactory that creates TweetSharpTwitterClients. This should bring me closer to my goal (testability) because i can switch the factory to MockTwitterClientFactory that creates a MockTwitterClient, that delivers some test output.
Now, let me come to my point.
I am using MEF for dependency injection (but i'm rather new to it). What I'm doing is this:
public class TwitterCrawler : CrawlerBase, ICrawler
{
[Import]
public ITwitterClientFactory TwitterClientFactory {get; set;}
public override Process()
{
ITwitterClient twitterClient = TwitterClientFactory.MakeSingletonClient();
// do something with twitterClient
}
}
Whereas my DefaultTwitterClientFactory exports itself to MEF:
[Export(typeof(ITwitterClient))]
public class DefaultTwitterClientFactory: ITwitterClientFactory
{
// implementation of ITwitterClientFactory
// provides methods to create instances of ITwitterClient implementations
}
Now, while this works so far, my question is, how to switch the factory?
How can i create a unit test and use the MockClientFactory instead of the DefaultTwitterClientFactory?
Is my approach good at all? Is it better to manually set the factory that is to be used?
Somewhere something like
... new TwitterCrawler(mockedTwitterClientFactory)
or even
.... new TwitterCrawler(mockedTwitterClient)?
This actually only moves the problem outside of TwitterClient, but still somewhere i have to decide how to construct the ITwitterClient and what factory to use for that purpose.
Should i dive more into the mechanics of MEF (ExportProvider?)
You shouldn't need to use the composer/container in your unit tests - just wire the SUT directly with the Test Doubles.
Something like this:
var sut = new TwitterCrawler();
sut.TwitterClientFactory = new FakeTwitterClientFactory();
However, you should really refactor from Property Injection to Constructor Injection, as the property implies that the dependency is optional.
BTW, your DefaultTwitterClientFactory doesn't export itself, it exports ITwitterClient.

Resources