用AEM於內容片段的GraphQL API

瞭解如何使用Adobe Experience Manager(AEM)的內容片段做為GraphQL API的Cloud Service,AEM以進行無頭內容傳送。

因AEM為Cloud ServiceGraphQL API與內容片段搭配使用,很大程度上是以標準的開放原始碼GraphQL API為基礎。

使用中的GraphQL APIAEM,可在無頭CMS實作中,將內容片段有效率地傳送至JavaScript用戶端:

  • 避免重複的API要求,就像REST一樣,
  • 確保交付內容僅限於特定要求,
  • 允許大量傳送呈現為單一API查詢回應所需的內容。
注意

GraphQL目前用於Adobe Experience Manager()的兩個(單獨)情AEM形中作為Cloud Service:

GraphQL API

GraphQL是:

  • "…API的查詢語言,以及使用您現有資料完成這些查詢的執行時期。 GraphQL提供您API中資料的完整且易於理解的描述,讓客戶能夠要求確切的所需內容,而不需要其他內容,讓API隨著時間推移而更容易發展,並提供功能強大的開發人員工具。"。

    請參閱GraphQL.org

  • "…開放式規格,以提供有彈性的API圖層。 將GraphQL置於您現有的後端,以前所未有的速度建立產品……."。

    請參閱瀏覽GraphQL

  • 」…facebook於2012年在內部開發的資料查詢語言和規格,2015年在公開開發源地之前。它為基於REST的體系結構提供了替代方案,其目的是提高開發人員的生產力並將傳輸的資料量減至最少。 GraphQL由數百個各種規模的組織在生產中使用……"

    請參見GraphQL Foundation

有關GraphQL API的詳細資訊,請參見以下各節(以及其他許多資源):

用於實AEM施的GraphQL基於標準GraphQL Java庫。 請參閱:

GraphQL術語

GraphQL使用下列功能:

有關詳細資訊,請參閱(GraphQL.org)GraphQL簡介,包括 Best Practices

GraphQL查詢類型

使用GraphQL,您可以執行查詢以返回:

您也可以執行:

注意

可以使用GraphiQL IDE測試和調試GraphQL查詢。

GraphQL for AEM Endpoint

端點是用於訪問GraphQL的路AEM徑。 您(或您的應用程式)可以使用此路徑:

  • 訪問GraphQL模式,
  • 發送您的GraphQL查詢,
  • 接收響應(對您的GraphQL查詢)。

端點有兩種類型AEM:

  • 全域
    • 可供所有網站使用。
    • 此端點可使用所有租戶的所有內容片段模型。
    • 如果有任何內容片段模型應在租戶之間共用,則應在全域租戶下建立。
  • 租用戶:
    • 與租用戶配置相對應,如Configuration Browser中所定義。
    • 特定於指定的網站/專案。
    • 租用戶特定端點會使用該特定租用戶的內容片段模型與全球租用戶的內容片段模型。
注意

內容片段編輯器可允許一個租用戶的內容片段參考另一個租用戶的內容片段(透過政策)。

在這種情況下,並非所有內容都可以使用租用戶特定端點進行檢索。

內容作者應控制此情形;例如,考慮將共用內容片段模型放在「全域」租用戶下,可能會很有用。

GraphQL用於全局端點的存AEM儲庫路徑為:

/content/cq:graphql/global/endpoint

您的應用程式可在請求URL中使用下列路徑:

/content/_cq_graphql/global/endpoint.json

要為GraphQL啟用端點,AEM您需要:

啟用GraphQL端點

要啟用GraphQL端點,首先需要有適當的配置。 請參閱內容片段——設定瀏覽器

注意

如果未啟用內容片段模型的使用,則​Create​選項將不可用。

要啟用相應端點,請執行以下操作:

  1. 導覽至​ToolsSites,然後選擇​GraphQL

  2. 選擇 建立

  3. 將會開啟​建立新的GraphQL端點​對話框。 您可以在此處指定:

    • 名稱:端點的名稱;您可以輸入任何文字。
    • 使用GraphQL模式,由:使用下拉式清單來選取所需的網站/專案。
    注意

    對話方塊中會顯示下列警告:

    • 如果未妥善管理,GraphQL 端點可能會導致資料安全性和效能問題。在建立端點後,請務必設定適當的權限。
  4. 使用​Create​確認。

  5. 後續步驟​對話框將提供指向安全控制台的直接連結,以便確保新建立的端點具有適當的權限。

    注意

    每個人都能存取端點。 這可能會——尤其是在發佈例項上——造成安全性顧慮,因為GraphQL查詢可能會對伺服器造成沈重負載。

    您可以在端點上設定與您的使用案例相應的ACL。

發佈GraphQL端點

選擇新端點和​Publish,使其在所有環境中都可完全使用。

注意

每個人都能存取端點。

在發佈例項上,這可能會引起安全性的顧慮,因為GraphQL查詢會給伺服器造成沈重負載。

您必須在端點上設定與您的使用案例相應的ACL。

圖形QL介面

標準GraphQL介面的實現可用於GraphQLAEM。 這可與🔗一起安裝AEM。

此介面可讓您直接輸入並測試查詢。

例如:

  • http://localhost:4502/content/graphiql.html

它提供語法反白顯示、自動完成、自動建議等功能,以及歷史記錄和線上檔案:

GraphiQL接

安裝AEMGraphiQL介面

GraphiQL用戶介面可隨專用包AEM一起安裝:GraphiQL Content Package v0.0.6(2021.3)軟體包。

作者和發佈環境的使用案例

使用案例可視Cloud Service環AEM境類型而定:

  • 發佈環境;用於:

    • JS應用程式的查詢資料(標準使用案例)
  • 作者環境;用於:

    • 查詢資料以「進行內容管理」:
      • GraphQL AEM in as aCloud Service當前是只讀API。
      • REST API可用於CR(u)D操作。

權限

權限是存取「資產」所需的權限。

方案生成

GraphQL是強式型別的API,這表示資料必須依類型清楚地結構化和組織。

GraphQL規範提供了一系列指引,說明如何建立用於查詢特定實例上資料的強穩API。 為此,客戶端需要讀取Schema,該包含查詢所需的所有類型。

對於內容片段,GraphQL結構(結構和類型)基於​Enabled 內容片段模型及其資料類型。

注意

所有GraphQL結構(衍生自​Enabled​的內容片段模型)都可通過GraphQL端點讀取。

這表示您需要確保沒有敏感資料可供使用,因為敏感資料可能會以此方式洩露;例如,這包括在模型定義中可能顯示為欄位名稱的資訊。

例如,如果用戶建立了名為Article的內容片段模型,AEM則生成類型為ArticleModel的對象article。 此類型中的欄位對應於模型中定義的欄位和資料類型。

  1. 內容片段模型:

    用於GraphQL的內容片

  2. 對應的GraphQL模式(從GraphiQL自動文檔輸出):
    基於內容片段模型的GraphQL

    這表示產生的類型ArticleModel包含數個欄位

    • 其中3個由用戶控制:authormainreferencearticle

    • 其他欄位則會自動加入,AEM並代表提供特定內容片段相關資訊的實用方法;在此範例中,_path_metadata_variations。 這些幫助欄位標有前面的_,以區分用戶定義的內容和自動生成的內容。

  3. 當使用者根據文章模型建立內容片段後,就可透過GraphQL進行詢問。 如需範例,請參閱範例查詢(根據範例內容片段結構,以便與GraphQL搭配使用)。

在GraphQL中,AEM模式是靈活的。 這表示每次建立、更新或刪除內容片段模型時都會自動產生此片段。 更新內容片段模型時,也會重新整理資料結構快取。

Sites GraphQL服務會監聽(在背景)對內容片段模型所做的任何修改。 檢測到更新時,只會重新生成模式的該部分。 此最佳化可節省時間並提供穩定性。

例如,如果:

  1. 安裝包含Content-Fragment-Model-1Content-Fragment-Model-2的軟體包:

    1. 將生成Model-1Model-2的GraphQL類型。
  2. 然後修改Content-Fragment-Model-2:

    1. 只有Model-2 GraphQL類型會更新。

    2. Model-1則保持不變。

注意

請務必注意,以防您透過REST api或其他方式對內容片段模型進行大量更新。

模式通過與GraphQL查詢相同的端點服務,客戶端處理以GQLschema副檔名調用模式的事實。 例如,對/content/cq:graphql/global/endpoint.GQLschema執行簡單的GET請求將導致輸出具有Content-type的架構:text/x-graphql-schema;charset=iso-8859-1

方案生成——未發佈的模型

當內容片段巢狀化時,可能會發佈父項內容片段模型,但參考的模型則不會。

注意

UI可AEM防止此情況發生,但若以程式設計方式發佈,或使用內容封裝,則可能會發生此情況。

發生此情況AEM時,會為父內容片段模型產生​不完整​架構。 這表示從架構中移除與未發佈模型相關的片段參考。

欄位

在架構中,有兩個基本類別的個別欄位:

  • 您產生的欄位。

    選擇欄位類型會用來根據您的內容片段模型設定欄位。 欄位名稱取自​資料類型​的​屬性名稱​欄位。

    • 還有​Render As​屬性要考慮,因為用戶可以配置某些資料類型;例如,單行文字或多欄位。
  • GraphQL AEM for也生成了幫助欄位

    這些用來識別內容片段,或取得內容片段的詳細資訊。

欄位類型

GraphQL AEM for支援類型清單。 所有支援的內容片段模型資料類型和相應的GraphQL類型都表示:

內容片段模型——資料類型 GraphQL類型 說明
單行文字 字串、[字串] 用於簡單字串,例如作者名稱、位置名稱等。
多行文字 字串 用於輸出諸如文章主體的文本
數量 浮點、[浮點] 用於顯示浮點數和常規數
布林值 (Boolean) 布林函數 用於顯示複選框→簡單的true/false語句
日期和時間 日曆 用於以ISO 8086格式顯示日期和時間。 根據所選的類型,GraphQL中有三種可用AEM方式:onlyDateonlyTimedateTime
列舉 String 用於從建立模型時定義的選項清單中顯示選項
標記 [String] 用來顯示表示「標籤」的字串清單AEM
內容參考資料 字串 用於顯示指向另一個資產的路徑,位於
片段引用 A模型類型 用於引用某個「模型類型」的另一個「內容片段」(在建立模型時定義)

輔助欄位

除了用戶生成欄位的資料類型外,GraphQL AEM for還生成許多​helper​欄位,以幫助識別內容片段或提供有關內容片段的附加資訊。

路徑

路徑欄位用作GraphQL中的標識符。 它表示儲存庫內的「內容片段」資產的AEM路徑。 我們選擇此為內容片段的識別碼,因為它:

  • 是獨一無二的AEM,
  • 很容易被牽扯。

下列程式碼會顯示根據內容片段模型Person所建立之所有內容片段的路徑。

{
  personList {
    items {
      _path
    }
  }
}

若要擷取特定類型的單一內容片段,您還需要先決定其路徑。 例如:

{
  personByPath(_path: "/content/dam/path/to/fragment/john-doe") {
    item {
      _path
      firstName
      name
    }
  }
}

請參閱範例查詢——單一特定城市片段

中繼資料

透過GraphQLAEM也公開內容片段的中繼資料。 中繼資料是描述內容片段的資訊,例如內容片段的標題、縮圖路徑、內容片段的說明、建立日期等。

由於中繼資料是透過架構編輯器產生,因此沒有特定結構,因此TypedMetaData GraphQL類型已實作以公開內容片段的中繼資料。 TypedMetaData 公開按以下標量類型分組的資訊:

欄位
stringMetadata:[StringMetadata]!
stringArrayMetadata:[StringArrayMetadata]!
intMetadata:[IntMetadata]!
intArrayMetadata:[IntArrayMetadata]!
floatMetadata:[FloatMetadata]!
floatArrayMetadata:[FloatArrayMetadata]!
booleanMetadata:[BooleanMetadata]!
booleanArrayMetadata:[booleanArrayMetadata]!
calendarMetadata:[CalendarMetadata]!
calendarArrayMetadata:[CalendarArrayMetadata]!

每個標量類型代表單個名稱——值對或名稱——值對陣列,其中該對的值是其所分組的類型。

例如,如果您想要擷取內容片段的標題,我們知道此屬性是字串屬性,因此我們會查詢所有字串中繼資料:

要查詢元資料,請執行以下操作:

{
  personByPath(_path: "/content/dam/path/to/fragment/john-doe") {
    item {
      _path
      _metadata {
        stringMetadata {
          name
          value
        }
      }
    }
  }
}

如果查看生成的GraphQL模式,則可以查看所有元資料GraphQL類型。 所有型號類型都有相同的TypedMetaData

注意

正常元資料和陣列元資料之間的差異
請記住,StringMetadataStringArrayMetadata都引用儲存在儲存庫中的內容,而不是如何檢索它們。

因此,例如,通過調用stringMetadata欄位,您將收到儲存在儲存庫中的所有元資料的陣列作為String,如果調用stringArrayMetadata,您將收到儲存在儲存庫中的所有元資料的陣列作為String[]

請參閱中繼資料的範例查詢——列出標題為GB的獎項中繼資料。

變數

_variations欄位已實作,以簡化查詢內容片段的變化。 例如:

{
  personByPath(_path: "/content/dam/path/to/fragment/john-doe") {
    item {
      _variations
    }
  }
}

請參閱範例查詢——具有命名變數的所有城市

GraphQL變數

GraphQL允許將變數放在查詢中。 有關詳細資訊,請參閱GraphQL文檔中的變數

例如,要獲取具有特定變化類型Article的所有內容片段,可以在GraphiQL中指定變數variation

GraphQL變

### query
query GetArticlesByVariation($variation: String!) {
    articleList(variation: $variation) {
        items {
            _path
            author
        }
    }
}
 
### in query variables
{
    "variation": "uk"
}

GraphQL指令

在GraphQL中,有可能根據變數更改查詢,稱為GraphQL指令。

例如,您可以在查詢中根據變數includePrice包含adventurePrice欄位,以查詢所有AdventureModels

GraphQL指

### query
query GetAdventureByType($includePrice: Boolean!) {
  adventureList {
    items {
      adventureTitle
      adventurePrice @include(if: $includePrice)
    }
  }
}
 
### in query variables
{
    "includePrice": true
}

篩選

您也可以在GraphQL查詢中使用篩選來傳回特定資料。

篩選使用基於邏輯運算子和運算式的語法。

例如,以下(基本)查詢會篩選名稱為JobsSmith的所有人員:

query {
  personList(filter: {
    name: {
      _logOp: OR
      _expressions: [
        {
          value: "Jobs"
        },
        {
          value: "Smith"
        }
      ]
    }
  }) {
    items {
      name
      firstName
    }
  }
}

如需更多範例,請參閱:

GraphQL for AEM —— 擴展集摘要

使用GraphQL對查詢進行基本操AEM作,以符合標準GraphQL規範。 對於具有以下擴展AEM名的GraphQL查詢:

持續查詢(快取)

在準備具有POST請求的查詢後,可使用HTTP快取或CDN快取的GET請求來執行查詢。

這是必要的,因為POST查詢通常不進行快取,而且如果將查詢與GET搭配使用作為參數,則很可能會使參數對HTTP服務和中間體過大。

持久查詢必須始終使用與適當(租用戶)配置相關的端點;這樣,它們就可以使用其中一種或兩種:

  • 全局配置和端點
    查詢可存取所有內容片段模型。
  • 特定租用戶組態和端點
    建立特定租用戶設定的持續查詢時,需要對應的租用戶特定端點(以提供對相關內容片段模型的存取)。
    例如,若要為WKND租用戶特別建立持續查詢,必須事先建立對應的WKND特定租用戶組態,以及WKND特定端點。
注意

如需詳細資訊,請參閱設定瀏覽器中的啟用內容片段功能。

GraphQL持久性查詢​需要為適當的租用戶配置啟用。

例如,如果有一個名為my-query的特定查詢,它使用租用戶配置my-conf中的模型my-model:

  • 您可以使用my-conf特定端點建立查詢,然後將查詢保存為:
    /conf/my-conf/settings/graphql/persistentQueries/my-query
  • 您可以使用global端點建立相同的查詢,但查詢將保存為:
    /conf/global/settings/graphql/persistentQueries/my-query
注意

這是兩個不同的查詢——保存在不同的路徑下。

他們恰巧使用相同的模型,但是透過不同的端點。

以下是保存給定查詢所需的步驟:

  1. 將查詢PUTing到新的端點URL /graphql/persist.json/<config>/<persisted-label>來準備該查詢。

    例如,建立持續查詢:

    $ curl -X PUT \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -H "Content-Type: application/json" \
        "http://localhost:4502/graphql/persist.json/wknd/plain-article-query" \
        -d \
    '{
      articleList {
        items{
            _path
            author
            main {
                json
            }
        }
      }
    }'
    
  2. 此時,請檢查回應。

    例如,檢查是否成功:

    {
      "action": "create",
      "configurationName": "wknd",
      "name": "plain-article-query",
      "shortPath": "/wknd/plain-article-query",
      "path": "/conf/wknd/settings/graphql/persistentQueries/plain-article-query"
    }
    
  3. 然後,您可以透過GETing the URL /graphql/execute.json/<shortPath>來重播持續查詢。

    例如,使用持續查詢:

    $ curl -X GET \
        http://localhost:4502/graphql/execute.json/wknd/plain-article-query
    
  4. 將POSTing的持續查詢更新為現有的查詢路徑。

    例如,使用持續查詢:

    $ curl -X POST \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -H "Content-Type: application/json" \
        "http://localhost:4502/graphql/persist.json/wknd/plain-article-query" \
        -d \
    '{
      articleList {
        items{
            _path
            author
            main {
                json
            }
          referencearticle {
            _path
          }
        }
      }
    }'
    
  5. 建立包裝的普通查詢。

    例如:

    $ curl -X PUT \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -H "Content-Type: application/json" \
        "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-wrapped" \
        -d \
    '{ "query": "{articleList { items { _path author main { json } referencearticle { _path } } } }"}'
    
  6. 使用快取控制建立包裝的純查詢。

    例如:

    $ curl -X PUT \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -H "Content-Type: application/json" \
        "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-max-age" \
        -d \
    '{ "query": "{articleList { items { _path author main { json } referencearticle { _path } } } }", "cache-control": { "max-age": 300 }}'
    
  7. 使用參數建立持續查詢:

    例如:

    $ curl -X PUT \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -H "Content-Type: application/json" \
        "http://localhost:4502/graphql/persist.json/wknd/plain-article-query-parameters" \
        -d \
    'query GetAsGraphqlModelTestByPath($apath: String!, $withReference: Boolean = true) {
      articleByPath(_path: $apath) {
        item {
          _path
            author
            main {
            plaintext
            }
            referencearticle @include(if: $withReference) {
            _path
            }
          }
        }
      }'
    
  8. 使用參數執行查詢。

    例如:

    $ curl -X POST \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -H "Content-Type: application/json" \
        "http://localhost:4502/graphql/execute.json/wknd/plain-article-query-parameters;apath=%2fcontent2fdam2fwknd2fen2fmagazine2falaska-adventure2falaskan-adventures;withReference=false"
    
    $ curl -X GET \
        "http://localhost:4502/graphql/execute.json/wknd/plain-article-query-parameters;apath=%2fcontent2fdam2fwknd2fen2fmagazine2falaska-adventure2falaskan-adventures;withReference=false"
    
  9. 要在發佈時執行查詢,需要複製相關的持久樹

    • 使用POST進行複製:

      $curl -X POST   http://localhost:4502/bin/replicate.json \
        -H 'authorization: Basic YWRtaW46YWRtaW4=' \
        -F path=/conf/wknd/settings/graphql/persistentQueries/plain-article-query \
        -F cmd=activate
      
    • 使用包:

      1. 建立新包定義。
      2. 包括配置(例如/conf/wknd/settings/graphql/persistentQueries)。
      3. 建立套件。
      4. 複製包。
    • 使用複製/分發工具。

      1. 前往「散發」工具。
      2. 為配置選擇樹激活(例如/conf/wknd/settings/graphql/persistentQueries)。
    • 使用工作流(通過工作流啟動程式配置):

      1. 定義工作流啟動程式規則,用於執行將複製不同事件(例如,建立、修改等)上的配置的工作流模型。
  10. 在查詢設定開啟發佈後,就會套用相同的原則,只要使用發佈端點。

    注意

    對於匿名訪問,系統假定ACL允許「每個人」訪問查詢配置。

    如果不是這樣,它將無法執行。

    注意

    URL中的任何分號(";")都需要進行編碼。

    例如,如同在「執行持續查詢」的請求中:

    curl -X GET \ "http://localhost:4502/graphql/execute.json/wknd/plain-article-query-parameters%3bapath=%2fcontent2fdam2fwknd2fen2fmagazine2falaska-adventure2falaskan-adventures;withReference=false"
    

從外部網站查詢GraphQL端點

要從外部網站訪問GraphQL端點,您需要配置:

CORS篩選

注意

如需CORS資源分享政策的詳細概觀,請參AEM閱瞭解跨來源資源分享(CORS)

若要存取GraphQL端點,必須在客戶Git儲存庫中設定CORS原則。 若要這麼做,請新增適當的OSGi CORS設定檔,以用於所需的端點。

此配置必須指定必須授予訪問權的受信任網站源alloworiginalloworiginregexp

例如,要授予對https://my.domain的GraphQL端點和持久查詢端點的訪問權,可以使用:

{
  "supportscredentials":true,
  "supportedmethods":[
    "GET",
    "HEAD",
    "POST"
  ],
  "exposedheaders":[
    ""
  ],
  "alloworigin":[
    "https://my.domain"
  ],
  "maxage:Integer":1800,
  "alloworiginregexp":[
    ""
  ],
  "supportedheaders":[
    "Origin",
    "Accept",
    "X-Requested-With",
    "Content-Type",
    "Access-Control-Request-Method",
    "Access-Control-Request-Headers"
  ],
  "allowedpaths":[
    "/content/_cq_graphql/global/endpoint.json",
    "/graphql/execute.json/.*"
  ]
}

如果您已為端點配置虛名路徑,也可以在allowedpaths中使用該路徑。

反向連結篩選器

除了CORS設定外,必須設定「反向連結」篩選器,才能允許第三方主機的存取。

若要這麼做,請新增適當的OSGi反向連結篩選設定檔案,其中:

  • 指定可信網站主機名;allow.hostsallow.hosts.regexp,
  • 授予此主機名的訪問權限。

例如,若要授與反向連結my.domain的請求存取權,您可以:

{
    "allow.empty":false,
    "allow.hosts":[
      "my.domain"
    ],
    "allow.hosts.regexp":[
      ""
    ],
    "filter.methods":[
      "POST",
      "PUT",
      "DELETE",
      "COPY",
      "MOVE"
    ],
    "exclude.agents.regexp":[
      ""
    ]
}
注意

客戶仍有責任:

  • 僅授與受信任網域的存取權
  • 確保未公開任何敏感資訊
  • 不使用通配符[*]語法;這既將禁用對GraphQL端點的驗證訪問,也將它向全世界公開。
注意

所有GraphQL 方案(衍生自​已啟用​的內容片段模型)都可通過GraphQL端點讀取。

這表示您需要確保沒有敏感資料可供使用,因為敏感資料可能會以此方式洩露;例如,這包括在模型定義中可能顯示為欄位名稱的資訊。

驗證

請參閱內容片段AEM的遠端GraphQL查詢驗證

常見問題

出現的問題:

  1. :"GraphQL API與Query Builder APIAEM有何不同?"

    • :「AEMGraphQL API提供JSON輸出的完整控制,是查詢內容的業界標準。未來,AEM計畫投資AEMGraphQL API。"

教學課程——無AEM頭和GraphQL快速入門

正在尋找實作教學課程? 請參閱無頭和GraphQL<a1/AEM>端對端教學課程,說明如何在無頭CMS情境中使用外部應用程式AEM的GraphQL API來建立和公開內容。

本頁內容

Adobe Summit Banner

A virtual event April 27-28.

Expand your skills and get inspired.

Register for free
Adobe Summit Banner

A virtual event April 27-28.

Expand your skills and get inspired.

Register for free
Adobe Maker Awards Banner

Time to shine!

Apply now for the 2021 Adobe Experience Maker Awards.

Apply now
Adobe Maker Awards Banner

Time to shine!

Apply now for the 2021 Adobe Experience Maker Awards.

Apply now