Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types for (entity | valueObject) "toObject" method #145

Closed
hikinine opened this issue Apr 15, 2024 · 3 comments · Fixed by #146
Closed

Types for (entity | valueObject) "toObject" method #145

hikinine opened this issue Apr 15, 2024 · 3 comments · Fixed by #146
Labels
enhancement New feature or request

Comments

@hikinine
Copy link
Contributor

Saudações,

Entity.toObject method may not have much usability for production environments, but during development it is interesting to be able to check a serialized form of the Aggregate/Entity.

Proposal

Dynamically type the toObject function for the exact return of the autoMapper (primitive of all aggregates recursively).

Requirements

Include an method (or property) on base class (GettersAndSetters), OR, include a new method in each class.
this method should return Props type

something like

class GettersAndSetters<Props> implements ... {
   ...
   getRawProps(): Props {
     return this.props
   } 
}

The reason for this is that you cannot infer a type that is protected/private (maybe I'm wrong).

Once we have the Entity Props available

type SerializerEntityReturnType<ThisEntity extends Entity<any>> = ReturnType<ThisEntity['getRaw']>
type SerializerValueObjectReturnType<ThisValueObject extends ValueObject<any>> = ReturnType<ThisValueObject['getRaw']>

export type Serializer<Props> = {
	[key in keyof Props]:
	Props[key] extends ValueObject<any>
	? Serializer<SerializerValueObjectReturnType<Props[key]>>
	: Props[key] extends Entity<any>
	? Serializer<SerializerEntityReturnType<Props[key]>>
	: Props[key] extends Array<any>
	? Array<Serializer<ReturnType<Props[key][0]['getRaw']>>>
	: Props[key]
}

class Entity<Props> {
        //toObject within serializer
	toObject2(): Serializer<Props> {
		return this.autoMapper.entityToObj(this) as any;
	}
}

USAGE

interface NestedValueObjectProps {
	address: Address
	b: string
}
interface PostProps {
	title: Title // nested value object
	address: Address // nested value object
	multipleAddress: Address[] // nested array of value object


	singleComment: Comments // nested entity
	multipleComments: Comments[] // nested array of entities
	content: string
	whatever: number
	whatever2: boolean
	createdAt: Date
}
interface CommentsProps {
	fullName: FullName
	message: string
	createdAt: Date
}
class Address extends ValueObject<{
	street: string
	zip: string
	number: number
}> { }

class NestedValueObject extends ValueObject<NestedValueObjectProps> { }
class FullName extends ValueObject<string> { }
class Age extends ValueObject<number> { }
class Title extends ValueObject<string> { }

interface UserProps {
	fullName: FullName //works with ValueObject
	multiFullNames: FullName[] // works with arrays of ValueObjects

	age: Age
	multipleAges: Age[]

	nestedVo: NestedValueObject
	multipleNestedVo: NestedValueObject[]


	description: string // works with primary types
	createdAt: Date // works with objects

	post: Post
	multiplePost: Post[] // works with arrays of entities
}

class Comments extends Entity<CommentsProps> { }
class Post extends Entity<PostProps> { }
class User extends Entity<UserProps> { }

const user = new User({} as any)
const object = user.toObject2()
object.multiplePost[0].multipleAddress[0].number
 
@4lessandrodev 4lessandrodev added the enhancement New feature or request label Apr 16, 2024
@4lessandrodev
Copy link
Owner

4lessandrodev commented Apr 16, 2024

hey @hikinine,

Thank you so much for opening this issue! Your suggestion for improving the toObject method is greatly appreciated. Indeed, enhancing it in this way has been on radar, especially considering its limited typing capabilities up to the first level and its compatibility only with TypeScript versions prior to 5.1.

toObject

It's worth noting that the current implementation of toObject may not fully meet your expectations due to its limitations. Specifically, it only provides typing up to the first level, and it's compatible only with TypeScript versions prior to 5.1.

Moreover, implementing this enhancement might require adjustments to the automapper functionality. Currently, automapper shortens objects that have only one attribute, which could affect the expected behavior of the method.

For instance, given the following example:

type Props = { value: string };

class Name extends ValueObject<Props> {
   public static init(name: string): Name {
      return new Name({ value: name });
   }
}

const name = Name.init("Jane");

console.log(name.toObject());
// Output: "Jane"

The output will only be the value, as automapper optimizes the object size by shortening the path.

Entityt Example:

type Props = { name: Name, age: Age };

class User extends Entity<Props> {
   public static init(name: Name, age: Age): User {
      return new User({ name, age });
   }
}

const user = User.init(name, age);

const model = user.toObject();
// Output: { "name": "Jane", "age": 21 }

As pointed out, the output is
{ "name": "Jane", "age": 21 } instead of { "name": { "value": "Jane" }, "age": { "value": 21 } }

Another important point

Ensuring that the toObject method returns read-only objects would be a significant enhancement, preventing unintended modifications to the state.

By returning read-only objects, we can provide an additional layer of protection against accidental data manipulation, while still allowing access to the object's properties for inspection purposes.

For example, consider the following scenario:

const company = Company.toObject();
company.users.push(user); // This should ideally result in a compilation error due to read-only nature

With read-only objects, attempts to modify the object's properties directly would be flagged at compile time, promoting safer coding practices and reducing the risk of runtime errors.

@hikinine
Copy link
Contributor Author

Hi @4lessandrodev

As pointed out, the output is
{ "name": "Jane", "age": 21 } instead of { "name": { "value": "Jane" }, "age": { "value": 21 } }

Thats the goal, only the primitive object.
I've tried on my own and looks like autoMapper returns exactly the same result as Serializer types.

Another important point
Ensuring that the toObject method returns read-only objects would be a significant enhancement, preventing unintended modifications to the state.

Bem pensado,
looks like autoMapper already creates a safe copy object, not referenced to the own aggregate props. But yes, should be nice turn everything on the Serializer result as readonly just for not miss understading.

Design Concerns

Something that is bothering me is the need to create a "getRawProps" method. The serializer does not necessarily need an implementation of the Props return. Just props inference at the typescript level. But I couldn't think of anything better than the method (which could be an antipattern since the intention is to protect properties and allow access only with the domain layer intelligence)

Anyway, let me know what you think.

Should I open this pull request?

@4lessandrodev
Copy link
Owner

4lessandrodev commented Apr 16, 2024

@hikinine Absolutely, Feel free to go ahead and open it. We can then work on utilizing the existing toObject method to ensure that all type and value inference tests for the following scenarios pass:

  • Entity with more than one value object, and each value object with a single attribute.
  • Entity with multiple value objects, and each value object with more than one attribute.
  • Entity with simple attributes.
  • Entity with value objects of different types, arrays, dates, strings, objects, etc.

Additionally, let's make sure to include an example of read-only implementation as follows:

    public toObject(): Readonly<Props> {
        return Object.freeze({ ... });
    }

Let's collaborate on this to ensure comprehensive testing and adherence to the desired functionality. Looking forward to working on this together!

hikinine added a commit to hikinine/rich-domain-1 that referenced this issue Apr 18, 2024
…IEntity, IValueObject, IAutoMapper signatures
hikinine added a commit to hikinine/rich-domain-1 that referenced this issue Apr 18, 2024
4lessandrodev added a commit that referenced this issue Apr 18, 2024
…t-method

#145 Feat: AutoMapperSerializer - add types for to object method
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants