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

Mapping multiple php object representations for a single resource to one graphql ObjectType #607

Open
aszenz opened this issue Jun 23, 2023 · 4 comments

Comments

@aszenz
Copy link
Contributor

aszenz commented Jun 23, 2023

We use graphqlite's external type declaration feature to map entities to graphql object types but not all of our services return entities, some of them return dto's, we then have to map these dto's into another graphql type causing the api to have multiple types that actually represent the same underlying resource.

Example:

I would like the ProductEntity and ProductDto to map to the same graphql object type Product.

Basically I need a way to map multiple php objects to the same underlying graphql type.

Here's how i imagine it could work:

class ProductEntity {
 public int $id;
 public string $code;
 public array $descriptions;
}

class ProductDto {
 public int $id;
 public string $code;
}

#[Type(classes: [ProductEntity::class, ProductDto::class], name: 'Product')]
class ProductOutputType
{
    public function __construct(private ProductRepository $productRepository) { }

    #[Field]
    public function getCode(Product|ProductDto $product): string
    {
        return $product->code;
    }

    /**
     * @return string[]
     */
    #[Field]
    // Notice how we are forced to map multiple objects represented by a type union
    // Since dto doesn't contain descriptions we can simply fetch it from another service
    public function getDescriptions(ProductEntity|ProductDto $product): array
    {
        if($product instanceof ProductEntity) {
            return $product->descriptions;
        }
        return $this->productRepository->getDescriptions($product->id);
    }
}

I realize this may seem like a strange feature so I'm curious how other people are solving this issue, are they duplicating graphql types for a single resource or mapping everything manually to one type.

@aszenz aszenz changed the title Mapping multiple php object representations for a single resource to a one graphql ObjectType Mapping multiple php object representations for a single resource to one graphql ObjectType Jun 23, 2023
@oojacoboo
Copy link
Collaborator

@aszenz we likely have some similar scenarios, but handling it differently.

Firstly, I'd question why your services need to return DTOs that are so similar to your entities. Why not just instantiate and pass around the entity? I realize this may be a Doctrine managed entity, with other associated functionality. But, properly written Doctrine entities do not require persistence or management from Doctrine. That's what makes Doctrine and the DataMapper pattern great.

Additionally, there is the ExtendType attribute that allows you to add custom fields, or modify the output of field values in a GraphQL context. We use a fair number of these for various situations.

Some input on the above would be helpful.

@aszenz
Copy link
Contributor Author

aszenz commented Jun 24, 2023

Firstly, I'd question why your services need to return DTOs that are so similar to your entities

Our entities are not similar to dto's, they contain more fields and associations.

This causes two issues:

  • Entities are harder to instantiate, especially from raw sql queries
  • Our team prefers dtos to get the exact data they need for read models, this relates to DDD and reducing coupling, while this is nice for testing, in the api layer i would prefer to expose rich models that can be queried further even if the performance is slower for some fields

Additionally, there is the ExtendType attribute that allows you to add custom fields, or modify the output of field values in a GraphQL context. We use a fair number of these for various situations.

From my understanding it's useful for creating more fields in the schema than available on the object, we already use external type declarations so all of our type mappers are services. So how would we use ExtendType to get a single graphql type for two different objects?

We could map dtos instead of entities for the api layer, but we also have multiple dtos representing the same resources in different modules of the app.

Main challenge is how can I create a unified api model when the underlying domain services represent the same concepts (like Product) slightly differently.

@oojacoboo
Copy link
Collaborator

oojacoboo commented Jun 26, 2023

@aszenz I don't think I have the full picture here. I understand what you guys are doing and basically why, and that mostly sounds okay, even though I'd argue that some of it's probably not necessary, or even advantageous, while some of it is likely a big benefit.

That said, the issue is with your queries, correct?

We use DTOs for all of our mutations and that's worked well.

As for your queries... I'm guessing you have issues where you have a model/entity defined as a type and then are trying to get a DTO as a field/reference?

If you based your types on your entities primarily, and then used ExtendType on those entities to add support for your DTO relationships, I think that'd get the job done. And the same could be done on DTOs that need fields to your entity types.

@aszenz
Copy link
Contributor Author

aszenz commented Jul 6, 2023

If you based your types on your entities primarily, and then used ExtendType on those entities to add support for your DTO relationships, I think that'd get the job done. And the same could be done on DTOs that need fields to your entity types.

Yes that's certainly a valid option we could use, although it still creates two object types that i have to keep in sync then (i.e they contain the same fields)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants