Federated Entity Data Fetcher implementation (Spring for GraphQL users)

Problem statement: Need to write more code for implementing federated entity Data Fetcher using Spring for GraphQL framework.

Apollo-federation-jvm-spring-example: Spring GraphQL Federation Example

Expectation: New mapping annotation like @SchemaMapping to handle federated entity Data Fetcher

Our Custom Approach: To separate Data Fetchers based on cencerns, we followed the below approach. With this approach,

  • Teams can concentrate on their business logic.
  • Improve developer experience as the GraphQlConfig implementation can be done once in a common lib
  1. Create an Interface
public interface EntityDataFetcher {
	
	public static final String TYP_NAME = "__typename";
	
	public String getTypeName();
	public Class<?> getTypeClass();
	public Object fetch(@NotNull Map<String, Object> reference);

}
  1. Sample entity DataFetcher implementation
@Component
public class SampleDataFetcher implements EntityDataFetcher {
     
    public static final String SCHEMA_TYP_NM = "Sample";
     
    @Autowired
    private SampleService sampleService;
 
    @Override
    public String getTypeName() {
        return SCHEMA_TYP_NM; // Graph type, defined in schema
    }
 
    @Override
    public Class<?> getTypeClass() {
        return SampleResponseDTO.class; // Return object class, return by the data fetcher
    }
 
    @Override
    public Object fetch(@NotNull Map<String, Object> reference) {
        // Business logic to retrieve Sample entity
    }
 
}
  1. GraphQlConfig implementation to register DataFetchers
@Configuration
public class GraphQlConfig {
	
	@Autowired(required=false)
	private List<EntityDataFetcher> dataFetchers;
	
	@Bean
	public GraphQlSourceBuilderCustomizer federationTransform() {
		return builder -> builder.schemaFactory((registry, wiring) ->
	        Federation.transform(registry, wiring)
                .fetchEntities(getEntityDataFetchers())
                .resolveEntityType(getClassNameTypeResolver())
                .build()
			);
	}
	
	private DataFetcher<?> getEntityDataFetchers() {
		Map<String, EntityDataFetcher> map = getEntityDataFetchersMap();

		DataFetcher<?> entityDataFetcher = env -> {
			List<Map<String, Object>> representations = env.getArgument(_Entity.argumentName);
			return representations.stream().map(representation -> {
				final String typeName = (String) representation.get(EntityDataFetcher.TYP_NAME);
				if (map.containsKey(typeName)) {
					return map.get(typeName).fetch(representation);
				} else {
					return null;
				}
			}).collect(Collectors.toList());
		};
		return entityDataFetcher;
	}
	
	private ClassNameTypeResolver getClassNameTypeResolver() {
		ClassNameTypeResolver classNameTypeResolver = new ClassNameTypeResolver();
		if (dataFetchers != null) {
			dataFetchers.stream().forEach(d -> {			
				classNameTypeResolver.addMapping(d.getTypeClass(), d.getTypeName());
			});
		}
		return classNameTypeResolver;
	}
	
	private Map<String, EntityDataFetcher> getEntityDataFetchersMap() {
		Map<String, EntityDataFetcher> map = null;
		if (dataFetchers != null) {
			map = dataFetchers.stream().collect(Collectors.toMap(EntityDataFetcher::getTypeName, d -> d));
		} else {
			map = Collections.emptyMap();
		}
		return map;
	}
	
}
1 Like

Thank you for sharing the solution!

FYI spring-graphql v1.3 provides built-in support for federation so you won’t need this workaround - see Federation :: Spring GraphQL for details