IMapper

The mapper's function is to translate and/or convert a domain object into a persistence object

There are two methods: toDomain and toPersistence the first one receives as argument a persistence object and convert it to a domain object.

// Mapper to be injected on repository.
export class UserModelToDomainMapper implements TMapper<Model, User> {
	//
	map(model: Model): Result<User> {
		const nameOrError = UserNameValueObject.create(model.userName);
		const emailOrError = EmailValueObject.create(model.userEmail);
		const passOrError = PasswordValueObject.create(model.userPassword);
		const birthOrError = BirthdayValueObject.create(model.userBirthDay);

		const observer = ChangesObserver.init<unknown>();

		observer.add(nameOrError);
		observer.add(emailOrError);
		observer.add(passOrError);
		observer.add(birthOrError);

		const result = observer.getResult();

		if (result.isFailure) {
			return Result.fail(result.errorValue());
		}

		return User.create({
			ID: DomainId.create(model.id),
			userName: nameOrError.getResult(),
			userEmail: emailOrError.getResult(),
			userPassword: passOrError.getResult(),
			userBirthDay: birthOrError.getResult(),
			createdAt: model.createdAt,
			updatedAt: model.updatedAt,
			isDeleted: model.isDeleted,
		});
	}
}

// What about Domain to Persistence Conversion ???
// use your domain instance toObject method. e.g: user.toObject();
// OR

export class UserDomainToModelMapper implements TMapper<User, Model> {
	//
	map(domain: User): Result<Model> {
		const model: Model = {
			id: domain.id.uid,
			userName: domain.userName.toObject(),
			userEmail: domain.userEmail.toObject(),
			userPassword: domain.userPassword.toObject(),
			userBirthDay: domain.userBirthDay.toObject(),
			createdAt: domain.createdAt,
			updatedAt: domain.updatedAt,
			isDeleted: domain.isDeleted,
		};

		return Result.ok(model);
	}
}

Mapper with state

TMapper implements map method with state


import { State, TMapper, FactoryMethod, Result, DomainId } from 'types-ddd';

interface UserModel {
  id: string;
  name: string;
  age: number;
  createdAt?: Date;
  updatedAt?: Date;
}

class UserToDomainMapper extends State<UserModel> 
    implements TMapper<UserModel, UserDomainEntity> {

  map ( model: UserModel ): Result<UserDomainEntity> {

    this.startState();

    this.addState( 'age', AgeValueObject.create( model.age ) );
    this.addState( 'name', NameValueObject.create( model.name ) );

    const result = this.checkState();
    if ( result.isFailure ) {
      return Result.fail( result.error );
    }

    return UserDomainEntity.create( {
      ID: DomainId.create(model.id),
      age: this.getStateByKey<AgeValueObject>('age').getResult(),
      name: this.getStateByKey<NameValueObject>('name').getResult(),
      createdAt: model.createdAt,
      updatedAt: model.updatedAt
    })
  }
}

class UserToDomainFactory extends FactoryMethod<UserModel, UserDomainEntity> {
  protected create (): TMapper<UserModel, UserDomainEntity> {
    return new UserToDomainMapper();
  }
}

const model: UserModel = {
  id: 'b082f233-0600-4359-994d-31f63ea2fa39',
  age: 18,
  name: 'Neo'
}

const domainEntity = UserDomainEntity.build(model, new UserToDomainFactory());

State

Generate a state for any class that extends to it

import { State } from 'types-ddd';

// state has a generic type to define possible keys
State<UserModel>

// get all keys on state
return this.getState();

// add a result to state
this.addState<AgeValueObject>('age', AgeValueObject.create(age));

// clear all data on state
this.resetState();

// starts a new state
this.startState();

// check if a key exists on state
return this.exists(key);

// get a state by key always returns a value
return this.getStateByKey<AgeValueObject>( key );

// get a state by key returns a value or undefined
return this.getStateByKey<AgeValueObject, undefined>( key );

// get a state by key returns a callback if key is not found
return this.getStateByKey<AgeValueObject>( key, callback );

// check if all state is success
return this.checkState();

// count state lenth 
return this.getSize();

What if I have a possible undefined key

Example a user has a optional role on dto, but required on domain entity

import { BaseDomainEntity, State, Entity, UserNameValueObject, EmailValueObject } from 'types-ddd';

// REMEMBER THIS METHOD ON STATE
// get a state by key returns a callback if key is not found
return this.getStateByKey<AgeValueObject>( key, callback );


// AGGREGATE PROPS
interface UserProps extends BaseDomainEntity {
    name: UserNameValueObject;
    email: EmailValueObject;
    password: PasswordValueObject;
    role: RoleValueObject;
}


// AGGREGATE 
class UserDomainEntity extends Entity<UserProps> {
    private constructor(props: UserProps){
        super(props, UserDomainEntity.name)
    }
    
    get email(): EmailValueObject {
        return this.props.email;
    }
    
    get name(): UserNameValueObject {
        return this.props.name;
    }
    
    get password(): PasswordValueObject {
        return this.props.password;
    }
    
    get role(): RoleValueObject {
        return this.props.role;
    }
    
    public static create (props: UserProps): Result<UserDomainEntity, string> {        
        return Result.ok(new UserDomainEntity(props));
    }
}


// DTO
interface CreateUserDto {
    name: string;
    email: string;
    password: string;
    role?: 'ADMIN' | 'MEMBER';
}


// MAPPER TO BUILD A USER FROM MODEL
class UserToDomainMapper extends State<CreateUserDto> 
    implements TMapper<CreateUserDto, UserDomainEntity> {
        // MAP METHOD
        map(target: CreateUserDto): Result<UserDomainEntity> {
        
            // START STATE
            this.startState();
            
            // ADD VALUE OBJECT TO STATE
            this.addState('name', UserNameValueObject.create(target.name));
            this.addState('email', EmailValueObject.create(target.email));
            this.addState('password', PasswordValueObject.create(target.password));
            target.role && this.addState('role', RoleValueObject.create(target.role));
            
            // VALIDATE ALL STATE
            const result = this.checkState();
            if (result.isFailure) {
                return Result.fail(result.error);
            }
            
            // DEFAULT ROLE
            const defaultRole = RoleValueObject.create('MEMBER');
            
            // PROPS TO USER
            const role = this.getStateByKey<PasswordValueObject>('role', defaultRole).getResult();
            const name = this.getStateByKey<UserNameValueObject>('name').getResult();
            const email = this.getStateByKey<EmailValueObject>('email').getResult();
            const password = this.getStateByKey<PasswordValueObject>('password').getResult();
            const ID = DomainId.create();
            
            return UserDomainEntity.create({ ID, name, email, password, role });
        
        }
        
}

// FACTORY TO PROVIDE TO BUILDER
class UserToDomainFactory extends FactoryMethod<CreateUserDto, UserToDomainMapper> {
  protected create (): TMapper<CreateUserDto, UserToDomainMapper> {
    return new UserToDomainMapper();
  }
}

// DTO TO CREATE A NEW USER
const dto: CreateUserDto = {
    name: 'Neo';
    email: 'neo@matrix.com';
    password: 'M4TR1X-R355UR31T10N';
}

// BUILD A NEW DOMAIN USER FROM DTO
const domainEntity = UserDomainEntity.build(dto, new UserToDomainFactory());

// GET INSTANCE 
const userDomainEntity = domainEntity.getResult();

// REVERSE: FROM DOMAIN INSTANCE TO MODEL 
const model = userDomainEntity.toObject<UserModel>(new UserToModelFactory());

Last updated