Skip to content
Draft

q #4

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/*.md
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
# Welcome to Welds coding-challenge

## Introduction

Here at Weld we use [NestJS](https://nestjs.com/) for our applications. So this project also reflects that. On our front-end we use NextJS and GraphQL. For simplicity we have used the monorepo structure from NestJS.

Fork this repository and create your own repository to get started.

## Challenge

One of our customers wants us to help them build a pipeline for an API (select whichever you want from [Public APIs](https://github.com/public-apis/public-apis)). And they want us to setup a new data-pipeline for them to get information out and into their current data-warehouse.

To accomplish this you will build two services:

- **Data-streams**: Our API that can receive calls and issue commands to **worker**. This service also stores any information that our customer wants to fetch.
- **Worker:** Fetches the data from external API. Makes any transformations you see fit. And sends it back to **data-streams** for storage.

### Steps in challenge

- Configure a message protocol between the two services. You can get inspiration from the [nestjs docs.](https://docs.nestjs.com/microservices/basics) Choose which ever you want but tell us why in your answer.
- Create an endpoint on **data-streams** that tells **worker** to start fetching data on an interval (every 5 minutes).
- Setup an [http module](https://docs.nestjs.com/techniques/http-module) that **worker** can use to communicate with the external API.
- Send the data and store the results on **data-streams** using internal communication protocol.
- Make an endpoint on **data-streams** that can fetch the data stored on **data-streams**. Use whatever storage you see fit but tell us why you chose it.
- Make an endpoint on **data-streams** that can fetch the data stored on **data-streams**. Use whatever storage you see fit but tell us why you chose it.
- Make an endpoint on **data-streams** that can stop the data fetching on **worker**.

## How we evaluate

The test is solely for you to show techniques and design patterns you normally use. Once the techniques and design patterns have been demonstrated then that is enough. No neeed for additional boilerplate. Just include a future work section in your answer and we will include questions in the technical interview.

- We understand that this can be **time consuming**. If you are short on time - then leave something out. But be sure to tell us your approach to the problem in the documentation.
Expand All @@ -30,26 +35,32 @@ The test is solely for you to show techniques and design patterns you normally u
- We appreciate small commits with a trail of messages that shows us how you work.

## Project structure

```
├── README.md
├── apps
│   ├── data-streams
│   └── worker
├── package.json
```

### data-streams:

This is our API. We will be able to issue HTTP requests to this and have it talk to our microservice **worker**.
We also store any information that **worker** sends our way. This project has been setup as a hybrid app. It can both function as an API but also as a microservice with an internal communication layer.

You can start data-streams with:

```
yarn start
```

### worker:

This is the worker microservice that is in charge of talking to the external API. It will fetch data when issued a command from **data-streams** and then return the results. This project only functions as a microservice which means it can only receive commands from the internal communication layer.

You can start worker with:

```
yarn start worker
```
22 changes: 0 additions & 22 deletions apps/data-streams/src/app.controller.spec.ts

This file was deleted.

54 changes: 48 additions & 6 deletions apps/data-streams/src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Controller, Get, Post, Logger, Body } from '@nestjs/common';
import { DataStreamsService } from './app.service';
import { MessagePattern } from '@nestjs/microservices';
import { NobelPrizesEntity } from './nobel/entities/nobel.entity';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
export class DataStreamsController {
private readonly logger: Logger;

constructor(private readonly appService: DataStreamsService) {
this.logger = new Logger(DataStreamsController.name);
}

@Post('start')
startWorker() {
return this.appService.start();
}

@Post('stop')
stopWorker() {
return this.appService.stop();
}

@Get()
getHello(): string {
return this.appService.getHello();
async getData(): Promise<NobelPrizesEntity[]> {
try {
const res = await this.appService.fetchDataFromStorage();
this.logger.log(
'getData - DataStreamsController: Successfully fetched the data from persistance',
);
this.logger.log(res);
return res;
} catch (e) {
this.logger.error(
'getData - DataStreamsController: An error occured while fetching the data',
);
return [];
}
}

@MessagePattern('new_data_fetched')
handleRecivedData(@Body() data) {
try {
this.appService.persistData(data);
this.logger.log(
'handleData - DataStreamsController: Sucessfully persisted data recived from worker.',
);
} catch (e) {
this.logger.error(
'handleData - DataStreamsController: An error occurred while persisting the data',
);
}
}
}
37 changes: 32 additions & 5 deletions apps/data-streams/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DataStreamsController } from './app.controller';
import { DataStreamsService } from './app.service';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NobelPrizesEntity } from './nobel/entities/nobel.entity';
import { NobelModule } from './nobel/nobel.module';

@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
imports: [
ClientsModule.register([
{
name: 'WORKER_SERVICE',
transport: Transport.TCP,
options: {
port: 3001,
},
},
]),
TypeOrmModule.forRoot({
type: 'mariadb',
host: 'localhost',
port: 3306,
username: 'root',
password: 'admin',
database: 'dwhCodingInterview',
entities: [NobelPrizesEntity],
synchronize: true,
logging: true,
dropSchema: true,
}),
NobelModule,
],
controllers: [DataStreamsController],
providers: [DataStreamsService],
})
export class AppModule {}
38 changes: 34 additions & 4 deletions apps/data-streams/src/app.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Inject } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { NobelService } from './nobel/nobel.service';
import { NobelPrizesEntity } from './nobel/entities/nobel.entity';

@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
export class DataStreamsService {
intrevalId: number = 300000; // 5 min in ms

constructor(
@Inject('WORKER_SERVICE') private readonly client: ClientProxy,
private readonly nobelService: NobelService,
) {}

start() {
return this.client.emit('start_service', { interval: this.intrevalId });
}

stop() {
return this.client.emit('stop_service', {});
}

async fetchDataFromStorage() {
const retrivedData = await this.nobelService.findall();
return retrivedData;
}

persistData(data: object) {
const nobelPrizesObj = data['nobelPrizes'][0];
const nobelPrize = new NobelPrizesEntity();
nobelPrize.name = nobelPrizesObj['laureates'][0]['knownName']['en'] ?? '';
nobelPrize.category = nobelPrizesObj['category']['en'] ?? '';
nobelPrize.awardDate = new Date(nobelPrizesObj['dateAwarded']) ?? null;
nobelPrize.awardYear = nobelPrizesObj['awardYear'] ?? '';
nobelPrize.price = nobelPrizesObj['prizeAmount'] ?? 0;
this.nobelService.saveData(nobelPrize);
}
}
8 changes: 6 additions & 2 deletions apps/data-streams/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { AppModule } from './app.module';
import { INestApplication } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';

const initMicroservice = async (app: INestApplication) => {
app.connectMicroservice({
// Setup communication protocol here
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: {
port: 3000,
},
});
await app.startAllMicroservices();
};
Expand Down
25 changes: 25 additions & 0 deletions apps/data-streams/src/nobel/entities/nobel.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class NobelPrizesEntity {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@Column()
category: string;

@Column({
unique: false,
nullable: true,
})
awardDate: Date;

@Column()
awardYear: string;

@Column()
price: number;
}
4 changes: 4 additions & 0 deletions apps/data-streams/src/nobel/nobel.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';

@Controller()
export class NobelController {}
13 changes: 13 additions & 0 deletions apps/data-streams/src/nobel/nobel.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { NobelController } from './nobel.controller';
import { NobelService } from './nobel.service';
import { NobelPrizesEntity } from './entities/nobel.entity';

@Module({
imports: [TypeOrmModule.forFeature([NobelPrizesEntity])],
providers: [NobelService],
controllers: [NobelController],
exports: [NobelService],
})
export class NobelModule {}
20 changes: 20 additions & 0 deletions apps/data-streams/src/nobel/nobel.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { NobelPrizesEntity } from './entities/nobel.entity';
import { Repository } from 'typeorm';

@Injectable()
export class NobelService {
constructor(
@InjectRepository(NobelPrizesEntity)
private nobelRepository: Repository<NobelPrizesEntity>,
) {}

async findall(): Promise<NobelPrizesEntity[]> {
return await this.nobelRepository.find();
}

async saveData(nobelPrize: NobelPrizesEntity): Promise<any> {
return await this.nobelRepository.save(nobelPrize);
}
}
Loading