#concepts about DDD

18 messages · Page 1 of 1 (latest)

jaunty zodiac
#

I'm trying to apply object orientation in a study project, but see my problems. I have an Order class when creating an order my request has the following parameters ts { "clientId": "1234", "table": "30", "products": [ { "product": "a7f68a1c-acdb-4b4f-8037-3258b5050a14", "quantity": 1 } ] } When trying to use my domain class to create ```ts
@Injectable()
export class CreateOrderUseCase {
constructor(private readonly orderRepository: OrderRepository) {}

async execute(
clientId: string,
orderDetailsInput: OrderDetailsInput,
): Promise<Order> {
const { table, products } = orderDetailsInput;
const order = Order.create({ clientId, table, products });
await this.orderRepository.save(order);
return order;
}
}
Order classts
type ProductInput = {
productId: string;
quantity: number;
};

type Input = {
clientId: string;
table: string;
products: ProductInput[];
};

export class Order extends BaseEntity {
updatedAt?: Date;
finishedAt?: Date;

constructor(
readonly clientId: string,
private table: string,
private status: OrderStatus,
private input: ProductInput[],
) {
super();
this.clientId = clientId;
this.table = table;
}

static create({ clientId, table, products }: Input) {
const status = OrderStatus.WAITING;
return new Order(clientId, table, status, products);
}
Then persist the data in the database and this flow worksts
async save(order: Order): Promise<void> {
try {
console.log('ORDER', order);
const newOrder = await this.orderModel.create(order);
await newOrder.save({ validateBeforeSave: false });
} catch (error) {
this.logger.error(error.message);
throw new InternalServerErrorException();
}
}

#

I encounter the problem when I try to retrieve an Order, because I configured the repository to populate the product details ```ts
@Schema()
export class OrderModel extends Document {
@Prop({ type: UUID, required: true })
_id: string;
@Prop({ required: true })
table: string;
@Prop({ required: true })
status: OrderStatus;
@Prop([
{
product: { type: String, ref: 'ProductModel' },
quantity: Number,
},
])
products: ProductModel[];
@Prop({ required: true })
clientId: string;
@Prop({ required: true })
createdAt: Date;
@Prop({ required: false })
updatedAt: Date;
@Prop({ required: false })
finishedAt: Date;
}

export const OrderSchema = SchemaFactory.createForClass(OrderModel);
ts
async getAll(clientId: string): Promise<Order[]> {
try {
const orders = await this.orderModel
.find({ clientId })
.populate('products.product')
.exec();

  return OrderMapper.toDomainArray(orders);
} catch (error) {
  this.logger.error(error.message);
  throw new InternalServerErrorException();
}

}
At this point I can't map my return because when I try to map the model to the domain in the Order class it doesn't have the Product propertiests
export class OrderMapper {
static toDomain(order: OrderModel): Order {
/* const orderMapper = new Order(
order.clientId,
order.table,
order.status,
order.products.map((product) => ({
productId: product._id,
quantity: product.price,
name: product.name,
})),
);
orderMapper.id = order._id;

return orderMapper; */
return;

}

static toDomainArray(orders: OrderModel[]): Order[] {
return orders.map((order) => this.toDomain(order));
}
}

hazy hawk
#

Ok. You are unfortunately overcomplicating things it seems.

Some points to work on:

  1. What you call a model is an entity. The entity is usually just a class that can also be the type for the return data from the database.

  2. Mongoose, which it seems you are using, has the model type "built in". So, when you call on a model to be injected into a service, or as in I believe your case, a repository, Mongoose has it already built up for you.

  3. You shouldn't extend from Document. That is no longer (and never was) good practice (even though Nest had suggested it for so long). You should be doing this:

export type OrderDocument = HydratedDocument<Order>

This is shown in the example in the Nest documentation.

  1. Is there a requirement in your project to have repositories? Because with Mongoose, you don't need them.

  2. Your entity (what you've called model) should be effectively the schema for your database. It also is the type for your document (from point 3). That in turn is what you use as your return type from the repository or Mongoose model (you see why you don't use the term model now?).

  3. I'd suggest your "getAll" should be named "gatAllForClient". It's not getting all orders in general, but rather getting all orders for a particular client.

  4. This should be its own class.

  @Prop([
    {
      product: { type: String, ref: 'ProductModel' },
      quantity: Number,
    },
  ])
  products: ProductModel[];

The definition in the prop. Usually in an order, they are called "lineItems" too. 🙂

At this point I can't map my return
You shouldn't even need to do this. But, I can't say why from the info given.

That's a start. If you have more questions, shoot away.

jaunty zodiac
#

thanks @hazy hawk

#
  1. I wouldn't then need to have this division in the domain, have a class just with the business rules and below have the implementation entity there, which in my case I use Mongoose but it could be another orm
#
  1. Not necessarily, I tried to use just the concept of not having a direct association between the application core and the external world.
#
  1. I understand, but when I create an order I am saving the ID of that product and the quantity ordered in the bank, and I want the search for that order to bring me the data of the populated products such as name, image, etc.
#

This approach also becomes necessary when I need to say that I want it to accept saving a UUID type? ```ts
@Prop({ type: UUID, required: true })
_id: string;

mystic moon
#

I think you are mixing here the read side with the write side. DDD applies only to the write side and reading data with all necessary data for a particular client can mess things up when is built on the same part of the code. You can think about doing it in more CQRS way

jaunty zodiac
#

I understand, thanks for the opinion. I will study how I can apply

jaunty zodiac
#

@hazy hawk I managed to overcome this problem with your help, but something still makes me puzzle, and I think it's more about the concept of object orientation.

#

How to provide you with feature updates. An example I have a class called Person in it I have props and value objects. When receiving data from an update request, can I have parameters that have not changed and the correct changed parameters?

#

There should be a method for updating in the Person class or would it be the best way to create a new instance of Person passing the props to the constructor

hazy hawk
#

I'm sorry, but I'm not certain what it is you are trying to achieve. 🤷🏻‍♂️

jaunty zodiac
#

I'm just trying to apply the concept of encapsulating the rule of updating a resource within a domain, so to speak, it would be something ```ts
@Injectable()
export class EditPersonUseCase {
constructor(
@Inject('PersonDAO')
private readonly personDAO: PersonDAO,
@Inject('MapsService')
private readonly mapsService: MapsService,
) {}

async handle(id: string, editInput: EditInput): Promise<any> {
const person = await this.personDAO.searchById(id);
if (!person) throw new PersonNotFoundException(id);
const { address } = editInput;
console.log('editInput', editInput);

let coordinates: { lng: number; lat: number };
console.log(person.addressChanged(address));
if (person.addressChanged(address)) {
  console.log('ENTROU NO IF');
  coordinates = await this.mapsService.getCoordinates(address);
}
console.log('coordinates', coordinates);

// const updatedPerson = new Person({});
person.update({
  id,
  ...coordinates,
  //...(coordinates || {}), // Verifica se coordinates é definido
  ...editInput,
});
console.log('PERSON 2', person);
await this.personDAO.update(person);
return person;

}
}

#
export class Person extends BaseEntity {
  readonly name: Name;
  readonly email: Email;
  readonly dateOfBirth: Date;
  readonly gender: Gender;
  readonly profession: string;
  readonly contactNumbers: ContactNumbers;
  readonly address: Address;
  readonly photoUrl: string;
  readonly employer?: string;
  readonly createdAt: Date;
  private updatedAt?: Date;

  constructor(props: PersonProps) {
    console.log('PERSON CONSTRUCTOR', props);
    super(props.id);

    this.name = new Name(props.name);
    this.email = new Email(props.email);
    this.dateOfBirth = props.dateOfBirth;
    this.gender = props.gender;
    this.profession = props.profession;
    this.contactNumbers = new ContactNumbers(props.contactNumbers);
    this.address = new Address(props.address, props.address.coordinates);
    this.photoUrl = props.photoUrl;
    this.employer = props.employer;
    this.createdAt = props.createdAt || new Date();
  }

  static create(personData: PersonProps) {
    const person = new Person(personData);
    return person;
  }

  public update(newData: Partial<PersonProps>): void {
    Object.assign(this, newData);
    this.updatedAt = new Date();
  }

  public addressChanged(newAddress: Partial<Address>): boolean {
    console.log('addressChanged', newAddress);
    return !this.address.isEqual(newAddress);
  }
}
hazy hawk
#

Is there an absolute requirement to make your code this complicated? Cause, I look at it and it is difficult to tell where you are heading with it. I'm sorry, but I can't help you with this, as it isn't anything I can make recon of. Maybe someone else who has gone down the DDD rabbit hole can help better. Again, sorry.

jaunty zodiac
#

ok thanks