Skip to content

Commit 2afd107

Browse files
committed
wip
1 parent 2790a8a commit 2afd107

File tree

10 files changed

+247
-17
lines changed

10 files changed

+247
-17
lines changed

docker-compose.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ x-app-service: &default-app
77
- MONGO_URL=mongodb://mongo:27017/dev
88
depends_on:
99
- mongo
10+
- kibana
11+
- elasticsearch
1012
volumes:
1113
- .:/code:delegated
1214
- node_modules:/code/node_modules:delegated
@@ -21,6 +23,12 @@ services:
2123
ports:
2224
- 3000:3000
2325

26+
backoffice-backend:
27+
<<: *default-app
28+
command: bash -c "npm run build && npm run start:backoffice:backend"
29+
ports:
30+
- 3002:3000
31+
2432
backoffice-frontend:
2533
<<: *default-app
2634
command: bash -c "npm run build && npm run start:backoffice:frontend"
@@ -34,5 +42,34 @@ services:
3442
ports:
3543
- 27017:27017
3644

45+
elasticsearch:
46+
image: docker.elastic.co/elasticsearch/elasticsearch:7.9.3
47+
container_name: codely-elasticsearch
48+
environment:
49+
# - cluster.name=codely-elasticsearch
50+
- node.name=codely-elasticsearch
51+
- discovery.type=single-node #Elasticsearch forms a single-node cluster
52+
- bootstrap.memory_lock=true # might cause the JVM or shell session to exit if it tries to allocate more memory than is available!
53+
- 'ES_JAVA_OPTS=-Xms512m -Xmx512m'
54+
ulimits:
55+
memlock:
56+
soft: -1 # The memlock soft and hard values configures the range of memory that ElasticSearch will use. Setting this to –1 means unlimited.
57+
hard: -1
58+
volumes:
59+
- esdata:/usr/share/elasticsearch/data
60+
ports:
61+
- '9200:9200'
62+
63+
kibana:
64+
image: docker.elastic.co/kibana/kibana:7.8.1
65+
container_name: codely-kibana
66+
environment:
67+
ELASTICSEARCH_URL: http://codely-elasticsearch:9200
68+
ELASTICSEARCH_HOSTS: http://codely-elasticsearch:9200
69+
ports:
70+
- 5601:5601
71+
3772
volumes:
3873
node_modules:
74+
esdata:
75+
driver: local

package-lock.json

Lines changed: 45 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"cypress:run": "NODE_ENV=test ts-node tests/utils/cypress/run"
3232
},
3333
"dependencies": {
34+
"@elastic/elasticsearch": "^7.9.1",
3435
"@types/bson": "^4.0.2",
3536
"@types/compression": "^1.7.0",
3637
"@types/convict": "^5.2.1",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ElasticRepository } from '../../Shared/infrastructure/persistence/elasticsearch/ElasticRepository';
2+
import { BackofficeCourse } from '../domain/BackofficeCourse';
3+
import { BackofficeCourseRepository } from '../domain/BackofficeCourseRepository';
4+
import { Uuid } from '../../Shared/domain/value-object/Uuid';
5+
import { BackofficeCourseName } from '../domain/BackofficeCourseName';
6+
import { BackofficeCourseId } from '../domain/BackofficeCourseId';
7+
import { BackofficeCourseDuration } from '../domain/BackofficeCourseDuration';
8+
9+
type ElasticBackofficeCourseDocument = { _source: { id: string; duration: string; name: string } };
10+
11+
export class ElasticBackofficeCourseRepository
12+
extends ElasticRepository<BackofficeCourse>
13+
implements BackofficeCourseRepository {
14+
protected moduleName(): string {
15+
return 'backofficecourses';
16+
}
17+
18+
async searchAll(): Promise<BackofficeCourse[]> {
19+
const client = await this.client();
20+
21+
const response = await client.search({
22+
index: this.moduleName(),
23+
body: {
24+
query: {
25+
match_all: {}
26+
}
27+
}
28+
});
29+
30+
// console.log(response.body.hits.hits);
31+
32+
return response.body.hits.hits.map((hit: ElasticBackofficeCourseDocument) =>
33+
BackofficeCourse.fromPrimitives({ ...hit._source })
34+
);
35+
}
36+
37+
async save(course: BackofficeCourse) {
38+
return this.persist(this.moduleName(), course);
39+
}
40+
41+
async delete() {
42+
const client = await this.client();
43+
44+
return client.deleteByQuery({
45+
index: this.moduleName(),
46+
body: {
47+
query: {
48+
match_all: {}
49+
}
50+
}
51+
});
52+
}
53+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Client as ElasticClient } from '@elastic/elasticsearch';
2+
import config from '../../../../../apps/backoffice/backend/config/config';
3+
import { Nullable } from '../../../domain/Nullable';
4+
5+
export class ElasticClientFactory {
6+
private static clients: { [key: string]: ElasticClient } = {};
7+
8+
static async createClient(contextName: string): Promise<ElasticClient> {
9+
let client = ElasticClientFactory.getClient(contextName);
10+
11+
if (!client) {
12+
client = await ElasticClientFactory.createAndConnectClient();
13+
14+
ElasticClientFactory.registerClient(client, contextName);
15+
}
16+
17+
return client;
18+
}
19+
20+
private static getClient(contextName: string): Nullable<ElasticClient> {
21+
return ElasticClientFactory.clients[contextName];
22+
}
23+
24+
private static async createAndConnectClient(): Promise<ElasticClient> {
25+
const client = new ElasticClient({ node: config.get('elastic.url') });
26+
27+
return client;
28+
}
29+
30+
private static registerClient(client: ElasticClient, contextName: string): void {
31+
ElasticClientFactory.clients[contextName] = client;
32+
}
33+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Client as ElasticClient } from '@elastic/elasticsearch';
2+
import { AggregateRoot } from '../../../../Mooc/Courses/domain/AggregateRoot';
3+
4+
export abstract class ElasticRepository<T extends AggregateRoot> {
5+
constructor(private _client: Promise<ElasticClient>) {}
6+
7+
protected abstract moduleName(): string;
8+
9+
protected client(): Promise<ElasticClient> {
10+
return this._client;
11+
}
12+
13+
protected async persist(index: string, aggregateRoot: T): Promise<void> {
14+
const document = { ...aggregateRoot.toPrimitives() };
15+
16+
console.log('persist', document);
17+
(await this.client()).index({ index: index, body: document });
18+
}
19+
}

src/apps/backoffice/backend/config/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ const convictConfig = convict({
1414
env: 'MONGO_URL',
1515
default: 'mongodb://localhost:27017/backoffice-backend-dev'
1616
}
17+
},
18+
elastic: {
19+
url: {
20+
doc: 'The Elastic connection URL',
21+
format: String,
22+
env: 'ELASTIC_URL',
23+
default: 'http://localhost:9200'
24+
}
1725
}
1826
});
1927

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
services:
2-
Backoffice.Backend.courses.BackofficeCourseRepository:
3-
class: ../../../../../../Contexts/Backoffice/infrastructure/MongoBackofficeCourseRepository
4-
arguments: ['@Shared.ConnectionManager']
2+
Backoffice.Backend.courses.BackofficeCourseRepository:
3+
class: ../../../../../../Contexts/Backoffice/infrastructure/MongoBackofficeCourseRepository
4+
arguments: ['@Shared.ConnectionManager']
55

6-
Backoffice.Backend.courses.CoursesFinder:
7-
class: ../../../../../../Contexts/Backoffice/application/SearchAll/CoursesFinder
8-
arguments: ["@Backoffice.Backend.courses.BackofficeCourseRepository"]
9-
10-
Backoffice.Backend.courses.SearchAllCoursesQueryHandler:
11-
class: ../../../../../../Contexts/Backoffice/application/SearchAll/SearchAllCoursesQueryHandler
12-
arguments: ["@Backoffice.Backend.courses.CoursesFinder"]
13-
tags:
14-
- { name: 'queryHandler' }
6+
Backoffice.Backend.courses.BackofficeCourseRepository2:
7+
class: ../../../../../../Contexts/Backoffice/infrastructure/ElasticBackofficeCourseRepository
8+
arguments: ['@Shared.ConnectionManagerElastic']
159

10+
Backoffice.Backend.courses.CoursesFinder:
11+
class: ../../../../../../Contexts/Backoffice/application/SearchAll/CoursesFinder
12+
arguments: ['@Backoffice.Backend.courses.BackofficeCourseRepository2']
13+
14+
Backoffice.Backend.courses.SearchAllCoursesQueryHandler:
15+
class: ../../../../../../Contexts/Backoffice/application/SearchAll/SearchAllCoursesQueryHandler
16+
arguments: ['@Backoffice.Backend.courses.CoursesFinder']
17+
tags:
18+
- { name: 'queryHandler' }
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
services:
22
Shared.Logger:
3-
class: ../../../../../../Contexts/Shared/infrastructure/WinstonLogger
3+
class: ../../../../../../Contexts/Shared/infrastructure/WinstonLogger
44
arguments: []
55

66
Shared.ConnectionManager:
77
factory:
8-
class: ../../../../../../Contexts/Shared/infrastructure/persistence/mongo/MongoClientFactory
8+
class: ../../../../../../Contexts/Shared/infrastructure/persistence/mongo/MongoClientFactory
9+
method: 'createClient'
10+
arguments: ['mooc']
11+
12+
Shared.ConnectionManagerElastic:
13+
factory:
14+
class: ../../../../../../Contexts/Shared/infrastructure/persistence/elasticsearch/ElasticClientFactory
915
method: 'createClient'
1016
arguments: ['mooc']
1117

@@ -15,4 +21,4 @@ services:
1521

1622
Shared.QueryBus:
1723
class: ../../../../../../Contexts/Shared/infrastructure/QueryBus/InMemoryQueryBus
18-
arguments: ['@Shared.QueryHandlersInformation']
24+
arguments: ['@Shared.QueryHandlersInformation']
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import container from '../../../../src/apps/backoffice/backend/config/dependency-injection';
2+
import { ElasticBackofficeCourseRepository } from '../../../../src/Contexts/Backoffice/infrastructure/ElasticBackofficeCourseRepository';
3+
import { ElasticClientFactory } from '../../../../src/Contexts/Shared/infrastructure/persistence/elasticsearch/ElasticClientFactory';
4+
import { EnvironmentArranger } from '../../Shared/infrastructure/arranger/EnvironmentArranger';
5+
import { BackofficeCourseMother } from '../application/domain/BackofficeCourseMother';
6+
7+
const client = ElasticClientFactory.createClient('test');
8+
const repository: ElasticBackofficeCourseRepository = new ElasticBackofficeCourseRepository(client);
9+
const environmentArranger: Promise<EnvironmentArranger> = container.get('Backoffice.Backend.EnvironmentArranger');
10+
11+
beforeEach(async () => {
12+
await repository.delete();
13+
});
14+
15+
afterAll(async () => {
16+
// await repository.delete();
17+
});
18+
19+
describe('Search all courses', () => {
20+
it('should return the existing courses', async () => {
21+
const courses = [BackofficeCourseMother.random(), BackofficeCourseMother.random()];
22+
await Promise.all(courses.map(course => repository.save(course)));
23+
const expectedCourses = await repository.searchAll();
24+
expect(courses.length).toEqual(expectedCourses.length);
25+
expect(courses.sort()).toEqual(expectedCourses.sort());
26+
});
27+
});

0 commit comments

Comments
 (0)