Azure FunctionsとGrapheneでCosmosDBデータ操作を行う(2)

## 1. はじめに
「[Azure FunctionsとGrapheneでCosmosDBデータ操作を行う](https://qiita.com/tamo_breaker/items/0dfc82426b7842617ada)」ではAzure FunctionsでGraphQL APIのQueryオペレーションを実装していましたが、Mutationオペレーションを実装していなかったので、今回はMutationの実装を進めていきたいと思います。

前回までの事前準備や実施内容は[この記事](https://qiita.com/tamo_breaker/items/0dfc82426b7842617ada)と[この記事](https://qiita.com/tamo_breaker/items/38eae9e5a24fade35a36)を参照ください。


## 2. GraphQLクエリのミューテーションの定義
「[Azure FunctionsとGrapheneでCosmosDBデータ操作を行う](https://qiita.com/tamo_breaker/items/0dfc82426b7842617ada)」で実施してきたことに引き続き、[Graphene](https://graphene-python.org/)を利用してGraphQLのMutationを実装していきます。実装については[Graphene公式ドキュメント](https://docs.graphene-python.org/en/latest/types/mutations/)を参照していきます。

ミューテーションはデータソースに対する書き込みとその実行結果の取得を行う、GraphQLオペレーションになります。(クエリはデータソースに対する読み取り結果の取得のみ)

スキーマを定義し、次にリゾルバーを定義し、最後にリゾルバー内部にデータソースに対する処理を実装するという流れは前回同様ですが、リゾルバーの指定がQuery型で定義してきたものと若干異なります。

### 2.1. Mutationの各操作に対応するリゾルバー定義
GraphQLのMutation型の各操作(フィールド)とリゾルバー関数を対応づけるにあたり、操作毎にMutationを継承したクラスを用意しておきます。

```
#Mutation型の各操作と対応づけるリゾルバーの定義
class CreateItem(graphene.Mutation):
    class Arguments:
        item = ItemInput(required=True) #ミューテーションのAugument
    Output = Item #ミューテーションの実行結果の型
    def mutate(self, info, item): #Mutation実行時に呼び出される関数
        i = Itemオブジェクトを作成するDB操作
        return i
```

上記の例ではInput型のオブジェクトを利用しています。こちらはInputObjectTypeを継承したクラスを用意し、QueryやMutationの各操作にArgumentsとして渡せるようにします。

```
#idとmessageというフィールドを持つinput型の定義
class ItemInput(graphene.InputObjectType): #inputの定義
    id = graphene.String(required=True)
    message = graphene.String()
```


### 2.2. Mutation型の定義
GraphQLのMutation型もQuery型と同様にObjectTypeを継承したクラスとして用意します。
Mutation型ではフィールド名とMutationを継承したクラスを対応づけておくと、GraphQLクエリのミューテーションに記述されたフィールド名に対応するリゾルバーが呼び出されるという流れになります。(Mutation型とMutationを継承したクラスで紛らわしい...)

```
#createItemというフィールドを持つMutation型の定義
class Mutation(graphene.ObjectType):
    create_Item = CreateItem.Field()
```

なおsnake_caseはcamelCaseに[置き換えてくれるとのこと](https://docs.graphene-python.org/en/latest/types/schema/#auto-camelcase-field-names)。

### 2.3. ルートクエリ宣言とミューテーションの実行

GraphQLのクエリを実行するにあたり、SchemaクラスでmutationとMutation型を対応づけ(ルートクエリ型の宣言)しておくことで、実際にミューテーションを実行(executeメソッドの呼び出し)する際に対応するMutationのリゾルバーの関数(というかクラスか)が呼び出されて処理が実行されるようになります。これはQuery型の場合と同様の動きになります。

```
schema = graphene.Schema(
    query=Query,
    mutation=Mutation #ここを追加
)
schema.execute("GraphQLのミューテーション")
```

### 2.4. GraphQL関連のコード

今回新たに追加したGraphQL関連のコードは以下になります。
＃全体のコードは[GitHub](https://github.com/moyota/azurefunctions-graphql)にはPUSHしていますが整理中です。


```graphql/graphql.py
# input DBItemInput
class DBItemInput(graphene.InputObjectType):
    id = graphene.String(required=True)
    owner = graphene.String(required=True)
    partitionKey = graphene.ID(required=True)
    message = graphene.String()
    addition = graphene.String()

class CreateItem(graphene.Mutation):
    class Arguments:
        item = DBItemInput(required=True)
    Output = DbItem
    def mutate(self, info, item):
        results = DatabaseConnection().create_item(item).pop()
        i = DbItem.__new__(DbItem)
        i.__dict__.update(results)
        return i

class DeleteItem(graphene.Mutation):
    class Arguments:
        item = DBItemInput(required=True)
    Output = DbItem
    def mutate(self, info, item):
        results = DatabaseConnection().delete_item(item)
        if results.__len__() > 0:
            i = DbItem.__new__(DbItem)
            i.__dict__.update(results[0])
            return i
        return None

class UpsertItem(graphene.Mutation):
    class Arguments:
        item = DBItemInput(required=True)
    Output = DbItem
    def mutate(self, info, item):
        results = DatabaseConnection().upsert_item(item)
        i = DbItem.__new__(DbItem)
        i.__dict__.update(results) 
        return i

# type Mutation
class Mutation(graphene.ObjectType):
    create_Item = CreateItem.Field()
    delete_Item = DeleteItem.Field()
    upsert_Item = UpsertItem.Field()

class GraphQL:
    def __init__(self):
        self.schema = graphene.Schema(
            query=Query,
            mutation=Mutation
        )
        pass

    def query(self, query):
        results = self.schema.execute(query)
        return json.dumps(results.data)

```

上記のスキーマをGraphQLスキーマ定義言語で記述すると以下のような感じになります。

```
input DBItemInput {
  id: String!
  owner: String!
  partitionKey: String!
  message: String
  addition: String
}

type Mutation {
  createItem(DBItemInput!): DbItem
  deleteItem(DBItemInput!): DbItem
  upsertItem(DBItemInput!): DbItem
}

schema {
  query: Query
  mutation: Mutation
}

```


## 4. GraphQLミューテーションの実行確認
実装した関数にGraphQLのミューテーションを発行し、レスポンスを見たいと思います。実行方法については、
クエリの発行はcurlコマンドを使うほか、[GraphiQL](https://github.com/graphql/graphiql)や[Insomnia](https://github.com/getinsomnia/insomnia)を使う手があると思います。
　
curlコマンドの場合には以下でクエリを発行できます。

```
curl -X POST -H "Content-Type: application/json" -d '{"query": "mutation { createItem (item: {id: "id1", owner: "testuser", partitionKey: 1, message: "test message1", addition: "addtion message 1"}) { id message } }"}' http://localhost:7071/api/MyFunction
```

今回発行したGraphQLミューテーションは以下になります。Insomniaの場合はURLを指定し以下ミューテーションを発行するとレスポンスが得られます。

```
mutation {
  createItem(item: {id: "id1", owner: "testuser", partitionKey: 1, message: "test message1", addition: "addtion message 1"}) { 
    id
    message
  } 
}
```

Insomniaで確認すると以下のような結果になりました。
<img width="866" alt="graphene-test03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280717/8a72faf5-b993-f1d5-b66f-97263a9431e4.png">


GrahpQLの利用や実装を進めるにあたり、GraphQLの仕様や用語に関してもっと理解をしておきたいですね。


##参考情報
[Graphene](https://graphene-python.org/)
[Graphene Mutation](https://docs.graphene-python.org/en/latest/types/mutations/)