Skip to content

Commit

Permalink
remove EmailEntity
Browse files Browse the repository at this point in the history
  • Loading branch information
DPDS93CT committed Jul 3, 2024
1 parent 00292be commit 6b3490e
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 274 deletions.
33 changes: 33 additions & 0 deletions docs/persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Developer notes on persistence layer

## Mapping aggregates to entities

During development many aggregates were created and these aggregates have to be persisted as entities.
Therefore, repositories were built which typically contain at least a _mapAggregateToData_ method and a _mapDataToAggregate_ method.

### Troubleshooting

If you encounter following error while writing such a method, 'Type Option<string> is not assignable to type string' e.g. on the id:

```typescript
function mapAggregateToData(aggregate: Aggregate<boolean>): RequiredEntityData<Aggregate> {
return {
// Don't assign createdAt and updatedAt, they are auto-generated!
id: aggregate.id, //Here following error is shown: Type Option<string> is not assignable to type string
personId: rel(PersonEntity, aggregate.personId),
value: aggregate.value,
};
}
```
make sure that id is the primary key for the entity, additional declarations of primary key attributes may result in the error mentioned above, like e.g. in this entity:
```typescript
@Entity({ tableName: 'tableName' })
export class Entitiy extends TimestampedEntity {
//...
public [PrimaryKeyProp]?: ['anotherProp'];
}
```




7 changes: 5 additions & 2 deletions src/modules/email/domain/email-address.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { EmailID } from '../../../shared/types/index.js';
import { PersonID } from '../../../shared/types/index.js';

export class EmailAddress<WasPersisted extends boolean> {
public constructor(
public email: Persisted<EmailID, WasPersisted>,
public id: Persisted<string, WasPersisted>,
public readonly createdAt: Persisted<Date, WasPersisted>,
public readonly updatedAt: Persisted<Date, WasPersisted>,
public personId: PersonID,
public address: string,
public enabled: boolean,
) {}
Expand Down
54 changes: 35 additions & 19 deletions src/modules/email/domain/email-event-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { PersonRepository } from '../../person/persistence/person.repository.js'
import { EventModule, EventService } from '../../../core/eventbus/index.js';
import { EmailFactory } from './email.factory.js';
import { Email } from './email.js';
import { EmailID, PersonID, RolleID } from '../../../shared/types/index.js';
import { EmailAddressID, PersonID, RolleID } from '../../../shared/types/index.js';
import { EmailInvalidError } from '../error/email-invalid.error.js';
import { ClassLogger } from '../../../core/logging/class-logger.js';
import { EmailAddress } from './email-address.js';
Expand All @@ -37,16 +37,11 @@ import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.
import { EmailAddressNotFoundError } from '../error/email-address-not-found.error.js';

function getEmail(emaiGeneratorService: EmailGeneratorService, personRepository: PersonRepository): Email<true> {
const fakeEmailId: EmailID = faker.string.uuid();
return Email.construct(
fakeEmailId,
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
emaiGeneratorService,
personRepository,
[new EmailAddress<true>(fakeEmailId, faker.internet.email(), true)],
);
const fakePersonId: PersonID = faker.string.uuid();
const fakeEmailAddressId: string = faker.string.uuid();
return Email.construct(fakePersonId, emaiGeneratorService, personRepository, [
new EmailAddress<boolean>(fakeEmailAddressId, undefined, undefined, fakePersonId, faker.internet.email(), true),
]);
}

describe('Email Event Handler', () => {
Expand Down Expand Up @@ -141,7 +136,7 @@ describe('Email Event Handler', () => {
describe('when existing email is found', () => {
it('should enable existing email', async () => {
const fakePersonId: PersonID = faker.string.uuid();
const emailId: EmailID = faker.string.uuid();
const emailAddressId: EmailAddressID = faker.string.uuid();
const event: PersonenkontextCreatedEvent = new PersonenkontextCreatedEvent(
fakePersonId,
faker.string.uuid(),
Expand All @@ -152,7 +147,14 @@ describe('Email Event Handler', () => {
serviceProviderRepoMock.findByIds.mockResolvedValueOnce(spMap);

const emailAddresses: EmailAddress<true>[] = [
new EmailAddress<true>(emailId, faker.internet.email(), true),
new EmailAddress<true>(
emailAddressId,
faker.date.past(),
faker.date.recent(),
fakePersonId,
faker.internet.email(),
true,
),
];
// eslint-disable-next-line @typescript-eslint/require-await
emailRepoMock.findByPerson.mockImplementationOnce(async (personId: PersonID) => {
Expand All @@ -168,7 +170,7 @@ describe('Email Event Handler', () => {
get currentAddress(): Option<string> {
return '[email protected]';
},
id: emailId,
personId: fakePersonId,
emailAddresses: emailAddresses,
}),
};
Expand All @@ -187,7 +189,7 @@ describe('Email Event Handler', () => {
describe('when existing email is found but enabling results in error', () => {
it('should log error', async () => {
const fakePersonId: PersonID = faker.string.uuid();
const emailId: EmailID = faker.string.uuid();
const emailAddressId: EmailAddressID = faker.string.uuid();
const event: PersonenkontextCreatedEvent = new PersonenkontextCreatedEvent(
fakePersonId,
faker.string.uuid(),
Expand All @@ -198,7 +200,14 @@ describe('Email Event Handler', () => {
serviceProviderRepoMock.findByIds.mockResolvedValueOnce(spMap);

const emailAddresses: EmailAddress<true>[] = [
new EmailAddress<true>(emailId, faker.internet.email(), true),
new EmailAddress<true>(
emailAddressId,
faker.date.past(),
faker.date.recent(),
fakePersonId,
faker.internet.email(),
true,
),
];
// eslint-disable-next-line @typescript-eslint/require-await
emailRepoMock.findByPerson.mockImplementationOnce(async (personId: PersonID) => {
Expand Down Expand Up @@ -239,7 +248,16 @@ describe('Email Event Handler', () => {

emailFactoryMock.createNew.mockImplementationOnce((personId: PersonID) => {
const emailMock: DeepMocked<Email<false>> = createMock<Email<false>>({
emailAddresses: [new EmailAddress<false>(undefined, faker.internet.email(), true)],
emailAddresses: [
new EmailAddress<false>(
undefined,
undefined,
undefined,
personId,
faker.internet.email(),
true,
),
],
personId: personId,
});
const emailAddress: EmailAddress<false> = createMock<EmailAddress<false>>({
Expand Down Expand Up @@ -374,8 +392,6 @@ describe('Email Event Handler', () => {

describe('when deletion is successful', () => {
it('should log info', async () => {
emailRepoMock.deleteById.mockResolvedValueOnce(true);

await emailEventHandler.asyncPersonDeletedEventHandler(event);

expect(loggerMock.info).toHaveBeenCalledWith(`Successfully deactivated email-address:${emailAddress}`);
Expand Down
2 changes: 1 addition & 1 deletion src/modules/email/domain/email-event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { ServiceProvider } from '../../service-provider/domain/service-provider.
import { ServiceProviderTarget } from '../../service-provider/domain/service-provider.enum.js';
import { EmailFactory } from './email.factory.js';
import { Email } from './email.js';
import { EmailRepo } from '../persistence/email.repo.js';
import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.js';
import { DomainError } from '../../../shared/error/index.js';
import { PersonID } from '../../../shared/types/index.js';
import { EmailAddressEntity } from '../persistence/email-address.entity.js';
import { EmailAddressNotFoundError } from '../error/email-address-not-found.error.js';
import { EmailRepo } from '../persistence/email.repo.js';

@Injectable()
export class EmailEventHandler {
Expand Down
18 changes: 2 additions & 16 deletions src/modules/email/domain/email.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,8 @@ export class EmailFactory {
private readonly personRepository: PersonRepository,
) {}

public construct(
id: string,
createdAt: Date,
updatedAt: Date,
personId: PersonID,
emailAddresses: EmailAddress<true>[],
): Email<true> {
return Email.construct(
id,
createdAt,
updatedAt,
personId,
this.emailGeneratorService,
this.personRepository,
emailAddresses,
);
public construct(personId: PersonID, emailAddresses: EmailAddress<true>[]): Email<true> {
return Email.construct(personId, this.emailGeneratorService, this.personRepository, emailAddresses);
}

public createNew(personId: PersonID): Email<false> {
Expand Down
21 changes: 11 additions & 10 deletions src/modules/email/domain/email.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { faker } from '@faker-js/faker';
import { Person } from '../../person/domain/person.js';
import { EmailInvalidError } from '../error/email-invalid.error.js';
import { EmailAddress } from './email-address.js';
import { EmailID } from '../../../shared/types/index.js';
import { EmailAddressID } from '../../../shared/types/index.js';

describe('Email Aggregate', () => {
let module: TestingModule;
Expand Down Expand Up @@ -48,17 +48,18 @@ describe('Email Aggregate', () => {
describe('enable', () => {
describe('when emailAddresses are already present on aggregate', () => {
it('should return successfully', async () => {
const emailId: EmailID = faker.string.uuid();
const emailAddressId: EmailAddressID = faker.string.uuid();
const emailAddresses: EmailAddress<true>[] = [
new EmailAddress<true>(emailId, faker.internet.email(), false),
new EmailAddress<true>(
emailAddressId,
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
faker.internet.email(),
false,
),
];
const existingEmail: Email<true> = emailFactory.construct(
emailId,
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
emailAddresses,
);
const existingEmail: Email<true> = emailFactory.construct(faker.string.uuid(), emailAddresses);

const result: Result<Email<true>> = await existingEmail.enable();

Expand Down
51 changes: 14 additions & 37 deletions src/modules/email/domain/email.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EmailID, PersonID } from '../../../shared/types/index.js';
import { PersonID } from '../../../shared/types/index.js';
import { EmailGeneratorService } from './email-generator.service.js';
import { PersonRepository } from '../../person/persistence/person.repository.js';
import { Person } from '../../person/domain/person.js';
Expand All @@ -7,9 +7,6 @@ import { EmailAddress } from './email-address.js';

export class Email<WasPersisted extends boolean> {
private constructor(
public readonly id: Persisted<EmailID, WasPersisted>,
public readonly createdAt: Persisted<Date, WasPersisted>,
public readonly updatedAt: Persisted<Date, WasPersisted>,
public readonly personId: PersonID,
public readonly emailGeneratorService: EmailGeneratorService,
public readonly personRepository: PersonRepository,
Expand All @@ -21,43 +18,24 @@ export class Email<WasPersisted extends boolean> {
emailGeneratorService: EmailGeneratorService,
personRepository: PersonRepository,
): Email<false> {
return new Email<false>(
undefined,
undefined,
undefined,
personId,
emailGeneratorService,
personRepository,
undefined,
);
return new Email<false>(personId, emailGeneratorService, personRepository, undefined);
}

public static construct<WasPersisted extends boolean = true>(
id: string,
createdAt: Date,
updatedAt: Date,
personId: PersonID,
emailGeneratorService: EmailGeneratorService,
personRepository: PersonRepository,
emailAddresses: EmailAddress<true>[],
emailAddresses: EmailAddress<boolean>[],
): Email<WasPersisted> {
return new Email(id, createdAt, updatedAt, personId, emailGeneratorService, personRepository, emailAddresses);
return new Email(personId, emailGeneratorService, personRepository, emailAddresses);
}

public async enable(): Promise<Result<Email<WasPersisted>>> {
if (this.emailAddresses && this.emailAddresses[0]) {
this.emailAddresses[0].enabled = true;
return {
ok: true,
value: new Email(
this.id,
this.createdAt,
this.updatedAt,
this.personId,
this.emailGeneratorService,
this.personRepository,
this.emailAddresses,
),
value: new Email(this.personId, this.emailGeneratorService, this.personRepository, this.emailAddresses),
};
}
const person: Option<Person<true>> = await this.personRepository.findById(this.personId);
Expand All @@ -77,18 +55,17 @@ export class Email<WasPersisted extends boolean> {
error: new EmailInvalidError(),
};
}
const newEmailAddress: EmailAddress<false> = new EmailAddress<false>(undefined, generatedName.value, true);
const newEmailAddress: EmailAddress<boolean> = new EmailAddress(
undefined,
undefined,
undefined,
this.personId,
generatedName.value,
true,
);
return {
ok: true,
value: new Email(
this.id,
this.createdAt,
this.updatedAt,
this.personId,
this.emailGeneratorService,
this.personRepository,
[newEmailAddress],
),
value: new Email(this.personId, this.emailGeneratorService, this.personRepository, [newEmailAddress]),
};
}

Expand Down
17 changes: 9 additions & 8 deletions src/modules/email/persistence/email-address.entity.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { BaseEntity, Entity, ManyToOne, PrimaryKeyProp, Property, Rel } from '@mikro-orm/core';
import { EmailEntity } from './email.entity.js';
import { Entity, ManyToOne, Property, Ref } from '@mikro-orm/core';
import { PersonEntity } from '../../person/persistence/person.entity.js';
import { TimestampedEntity } from '../../../persistence/timestamped.entity.js';

@Entity({ tableName: 'email_address' })
export class EmailAddressEntity extends BaseEntity {
export class EmailAddressEntity extends TimestampedEntity {
@ManyToOne({
columnType: 'uuid',
fieldName: 'email_id',
fieldName: 'person_id',
ref: true,
entity: () => EmailEntity,
nullable: true,
deleteRule: 'set null',
entity: () => PersonEntity,
})
public email!: Rel<EmailEntity>;
public personId!: Ref<PersonEntity>;

@Property({ primary: true, nullable: false, unique: true })
public address!: string;

@Property({ nullable: false })
public enabled!: boolean;

public [PrimaryKeyProp]?: ['address'];
}
25 changes: 0 additions & 25 deletions src/modules/email/persistence/email.entity.ts

This file was deleted.

Loading

0 comments on commit 6b3490e

Please sign in to comment.