GraphQL(二):GraphQL服務(wù)搭建

GraphQL(一):GraphQL介紹中講到目前已經(jīng)有很多平臺完成了GraphQL實現(xiàn)勤揩,這里以Java平臺為例泉蝌,介紹GraphQL服務(wù)的搭建。

graphql-java + graphql-java-spring

graphql-java是GraphQL的Java實現(xiàn)笔咽,它實現(xiàn)了GraphQL的執(zhí)行茬暇,但是沒有任何關(guān)于HTTP或者JSON的處理,因此在接入SpringBoot時還需要graphql-java-spring的支持敢课。官方的案例就是使用這兩個jar包完成的阶祭。

在官方的案例中绷杜,我們需要實例化一個GraphQL實例:

@Component
public class GraphQLProvider {

    @Autowired
    GraphQLDataFetchers graphQLDataFetchers;

    private GraphQL graphQL;

    @PostConstruct
    public void init() throws IOException {
        URL url = Resources.getResource("schema.graphqls");
        String sdl = Resources.toString(url, Charsets.UTF_8);
        GraphQLSchema graphQLSchema = buildSchema(sdl);
        this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
    }

    private GraphQLSchema buildSchema(String sdl) {
        TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
        RuntimeWiring runtimeWiring = buildWiring();
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
    }

    private RuntimeWiring buildWiring() {
        return RuntimeWiring.newRuntimeWiring()
                .type(newTypeWiring("Query")
                        .dataFetcher("bookById", graphQLDataFetchers.getBookByIdDataFetcher()))
                .type(newTypeWiring("Book")
                        .dataFetcher("author", graphQLDataFetchers.getAuthorDataFetcher()))
                .build();
    }

    @Bean
    public GraphQL graphQL() {
        return graphQL;
    }

}

這樣的實現(xiàn)需要我們了解較多graphql-java的底層細(xì)節(jié),比如:TypeDefinitionRegistry濒募、RuntimeWiring鞭盟、SchemaGenerator等,同時還需要硬編碼字符串瑰剃。

同樣齿诉,在實現(xiàn)數(shù)據(jù)注入時,也需要硬編碼:

public DataFetcher getBookByIdDataFetcher() {
    return dataFetchingEnvironment -> {
        String bookId = dataFetchingEnvironment.getArgument("id");
        return books
                .stream()
                .filter(book -> book.get("id").equals(bookId))
                .findFirst()
                .orElse(null);
    };
}

public DataFetcher getAuthorDataFetcher() {
    return dataFetchingEnvironment -> {
        Map<String, String> book = dataFetchingEnvironment.getSource();
        String authorId = book.get("authorId");
        return authors
                .stream()
                .filter(author -> author.get("id").equals(authorId))
                .findFirst()
                .orElse(null);
    };
}

于是就有了 graphql-spring-boot-starter + graphql-java-tools 搭建GraphQL的方案晌姚。

graphql-spring-boot-starter + graphql-java-tools

graphql-java-tools

graphql-java-tools 能夠從GraphQL的模式定義 *.graphqls 文件構(gòu)建出對應(yīng)的Java的POJO類型對象(graphql-java-tools將讀取classpath下所有以*.graphqls為后綴名的文件粤剧,創(chuàng)建GraphQLSchema對象),同時為我們屏蔽了graphql-java的底層細(xì)節(jié)挥唠,它本身依賴graphql-java抵恋。

graphql-spring-boot-starter

graphql-spring-boot-starter是輔助SpringBoot接入GraphQL的庫,它本身依賴graphql-java和graphql-java-servlet(將GraphQL服務(wù)發(fā)布為通過HTTP可訪問的Web服務(wù)宝磨,封裝了一個GraphQLServlet接收GraphQL請求弧关,并提供Servlet Listeners功能)。

接下來我們將實現(xiàn)一個基于 graphql-spring-boot-starter + graphql-java-tools 搭建GraphQL服務(wù)的Demo唤锉。

Demo

1. 在pom中增加以下依賴

<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphiql-spring-boot-starter</artifactId>
    <version>3.6.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>3.6.0</version>
</dependency>

對應(yīng)的SpringBoot版本是1.5.6

2. 增加Teacher實體

@Serialization
data class Teacher(
        val id: String = "commonId",
        var teacherId: String = "",
        var teacherName: String = "",
        var teacherPhone: String = "",
        var schoolId: String = ""
) {
    override fun toString(): String {
        return JSON.toJSONString(this)
    }
}

以及對應(yīng)的Dao梯醒、Service、teacher.xml等

3. 在classpath下新建schema.graphqls

type School {
    id: ID!
    schoolId: String
    schoolName: String
    schoolAge: Int
    schoolAddress: String
    teachers: [Teacher]
    master: String
}

type Teacher{
    teacherId: String
    teacherName: String
    teacherPhone: String
    schoolId:  String
}

input TeacherInput{
    teacherId: String
    teacherName: String
    teacherPhone: String
    schoolId:  String
}

這里的模型最好和Java Bean一致腌紧,如果Java bean中有多余的字段,將被忽略畜隶,不會拋出異常壁肋。

4. 在classpath下新建root.graphqls

這是公開API的地方,按照GraphQL的規(guī)范籽慢,Query浸遗、Mutation、Subscription三種查詢類型需要放在各自的節(jié)點下(這里暫時不考慮訂閱):

type Query{
    # 根據(jù)學(xué)校Id查詢學(xué)校箱亿,schoolId不能為空跛锌,返回的School不能為空
    school(schoolId:String!):School!
}

type Mutation {
    insertSchool(schoolId: String!,schoolName:String!,schoolAge:Int!,schoolAddress:String!) : School!
    insertTeacher(teacher:TeacherInput!):Teacher!
}

5. 實現(xiàn)Resolver

graphql-java-tools為我們屏蔽了底層細(xì)節(jié),我們只需要繼承以下幾個類完成數(shù)據(jù)注入即可:

  • GraphQLQueryResolver
  • GraphQLMutationResolver
  • GraphQLSubscriptionResolver

Resolver完成的是數(shù)據(jù)的注入届惋,也就是對*.graphqls文件中的type的字段的數(shù)據(jù)進(jìn)行注入髓帽,注入需要滿足以下規(guī)則:

1. <field>
2. is<field> – only if the field is of type Boolean
3. get<field>

比如我們我們根據(jù)學(xué)校Id查詢學(xué)校的API:

@Component
class SchoolQueryResolver : GraphQLQueryResolver {

    @Autowired
    private lateinit var schoolService: SchoolService

    fun school(schoolId: String): School {
        return schoolService.getSchoolBySchoolId(schoolId)
    }

    //或者
    fun getSchool(schoolId: String): School {
        return schoolService.getSchoolBySchoolId(schoolId)
    }
}

我們在schema.graphqls中定義的類型有與之對應(yīng)的Java Bean,這些Java Bean都提供了getField方法脑豹,因此不需要額外實現(xiàn)Resolver郑藏,有時候,在type中定義的類型的某個字段數(shù)據(jù)的獲取比較麻煩瘩欺,不是簡單的getField可以解決的必盖,此時可以為此類型實現(xiàn)專門的字段值獲取的Resolver拌牲,假設(shè)School中的master字段邏輯獲取邏輯很復(fù)雜:

public class SchoolResolver implements GraphQLResolver<School> {
    private SchoolDao schoolDao;
 
    public School getMaster(School school) {
        return schoolDao.getMasterById(school.getMasterId());
    }
}

泛型中需要指定類型,字段數(shù)據(jù)獲取的方法名稱規(guī)則和常規(guī)接口的規(guī)則一致歌粥,只是需要把該類型作為參數(shù)傳遞到方法內(nèi)塌忽,值得注意的是,如果客戶端沒有請求Master字段失驶,那么getMaster方法將不會被執(zhí)行土居。

實際上針對type中的每個Field都需要有g(shù)etField,使得Graphql能夠獲取到數(shù)據(jù)注入到返回的結(jié)果中突勇,如果針對此Field已經(jīng)實現(xiàn)了Resolver装盯,那么會優(yōu)先使用Resolver來注入數(shù)據(jù),此時可以省略掉getField(直接去掉School Bean中的master字段)不過還是建議將Java Bean和type中的Field一一對應(yīng)甲馋,便于維護(hù)埂奈。

以上是針對Query的Demo,關(guān)于Mutation請查看文本的源碼定躏,這里需要說明的是我們的insertSchool和insertTeacher有些不同:

insertSchool(schoolId: String!,schoolName:String!,schoolAge:Int!,schoolAddress:String!) : School!
insertTeacher(teacher:TeacherInput!):Teacher!

insertTeacher引入了一個新類型TeacherInput账磺,將需要傳遞到服務(wù)端的數(shù)據(jù)封裝起來,GraphQL的返回類型(Teacher)和輸入類型(TeacherInput)是不能共用的痊远,所以加上Input后綴加以區(qū)分垮抗,同樣的,針對TeacherInput也需要有對應(yīng)的Java Bean碧聪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冒版,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子逞姿,更是在濱河造成了極大的恐慌辞嗡,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滞造,死亡現(xiàn)場離奇詭異续室,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谒养,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門挺狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人买窟,你說我怎么就攤上這事丰泊。” “怎么了始绍?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵趁耗,是天一觀的道長。 經(jīng)常有香客問我疆虚,道長苛败,這世上最難降的妖魔是什么满葛? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮罢屈,結(jié)果婚禮上嘀韧,老公的妹妹穿的比我還像新娘。我一直安慰自己缠捌,他們只是感情好锄贷,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曼月,像睡著了一般谊却。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哑芹,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天炎辨,我揣著相機(jī)與錄音,去河邊找鬼聪姿。 笑死碴萧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的末购。 我是一名探鬼主播破喻,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盟榴!你這毒婦竟也來了曹质?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤擎场,失蹤者是張志新(化名)和其女友劉穎羽德,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顶籽,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年银觅,在試婚紗的時候發(fā)現(xiàn)自己被綠了礼饱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡究驴,死狀恐怖镊绪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洒忧,我是刑警寧澤蝴韭,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站熙侍,受9級特大地震影響榄鉴,放射性物質(zhì)發(fā)生泄漏履磨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一庆尘、第九天 我趴在偏房一處隱蔽的房頂上張望剃诅。 院中可真熱鬧,春花似錦驶忌、人聲如沸矛辕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聊品。三九已至,卻和暖如春几苍,著一層夾襖步出監(jiān)牢的瞬間翻屈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工擦剑, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留妖胀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓惠勒,卻偏偏與公主長得像赚抡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子纠屋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355