다음을 통해 공유


TripPin 2부 - REST 서비스에 대한 데이터 커넥터

이 다중 파트 자습서에서는 파워 쿼리에 대한 새 데이터 원본 확장의 생성에 대해 설명합니다. 이 자습서는 순차적으로 수행됩니다. 각 단원은 이전 단원에서 만든 커넥터를 기반으로 하여 커넥터에 새 기능을 증분 방식으로 추가합니다.

이 단원에서는 다음을 수행합니다.

  • Web.Contents를 사용하여 REST API를 호출하는 기본 함수 만들기
  • 요청 헤더를 설정하고 JSON 응답을 처리하는 방법 알아보기
  • Power BI Desktop을 사용하여 응답을 사용자에게 친숙한 형식으로 랭글

이 단원에서는 TripPin 서비스에 대한 OData 기반 커넥터( 이전 단원에서 만든)를 RESTful API에 대해 만드는 것과 유사한 커넥터로 변환합니다. OData는 RESTful API이지만 규칙 집합이 고정된 API입니다. OData의 장점은 스키마, 데이터 검색 프로토콜 및 표준 쿼리 언어를 제공한다는 것입니다. OData.Feed의 사용을 중단하면 이러한 기능을 직접 커넥터에 구축해야 합니다.

OData 커넥터의 요약

커넥터에서 OData 함수를 제거하기 전에 현재 수행하는 작업(대부분 백그라운드에서)을 빠르게 검토하여 서비스에서 데이터를 검색해 보겠습니다.

Visual Studio Code의 1부 에서 TripPin 확장 프로젝트를 엽니다. 쿼리 파일을 열고 다음 쿼리에 붙여넣습니다.

TripPin.Feed("https://services.odata.org/v4/TripPinService/Me")

Fiddler를 열고 Visual Studio Code에서 현재 파워 쿼리 파일을 평가합니다.

Fiddler에는 서버에 대한 세 가지 요청이 있습니다.

세 개의 OData 요청이 표시된 Fiddler 출력 화면의 스크린샷

  • /Me: 요청하는 실제 URL입니다.
  • /$metadata: 함수에서 자동으로 OData.Feed 호출하여 응답에 대한 스키마 및 형식 정보를 확인합니다.
  • /Me/BestFriend: /Me 싱글톤을 나열할 때 (열심히) 끌어온 필드 중 하나입니다. 이 경우 호출로 인해 204 No Content 상태가 발생했습니다.

M 평가는 대부분 지연됩니다. 대부분의 경우 데이터 값은 필요한 경우에만 검색/끌어오기됩니다. 값이 적극적으로 불러와지는 시나리오(예: /Me/BestFriend 사례)가 있습니다. 이 동작은 멤버에 대한 형식 정보가 필요할 때 발생하는 경향이 있으며 엔진은 값을 검색하고 검사하는 것 외에 형식을 결정하는 다른 방법이 없습니다. 처리를 지연시키는 것(즉, 조기 데이터 수집을 방지하는 것)은 M 커넥터의 성능을 높이는 주요 측면 중 하나입니다.

/Me 요청 응답의 JSON 형식 및 요청과 함께 전송된 요청 헤더를 살펴봅니다.

{
  "@odata.context": "https://services.odata.org/v4/TripPinService/$metadata#Me",
  "UserName": "aprilcline",
  "FirstName": "April",
  "LastName": "Cline",
  "MiddleName": null,
  "Gender": "Female",
  "Age": null,
  "Emails": [ "April@example.com", "April@contoso.com" ],
  "FavoriteFeature": "Feature1",
  "Features": [ ],
  "AddressInfo": [
    {
      "Address": "P.O. Box 555",
      "City": {
        "Name": "Lander",
        "CountryRegion": "United States",
        "Region": "WY"
      }
    }
  ],
  "HomeAddress": null
}

쿼리 평가가 완료되면 PQTest 결과 창에 Me 싱글톤에 대한 레코드 값이 표시됩니다.

PQTest 결과 스크린샷은 Me 싱글톤의 레코드 값을 보여 줍니다.

출력 창의 필드를 원시 JSON 응답에서 반환된 필드와 비교하면 일치하지 않는 것을 알 수 있습니다. 쿼리 결과에는 JSON 응답의 아무 곳에도 표시되지 않는 다른 필드(Friends, Trips, GetFriendsTrips)가 있습니다. OData.Feed 함수는 $metadata 반환된 스키마를 기반으로 이러한 필드를 레코드에 자동으로 추가했습니다. 이러한 차이는 커넥터가 더 나은 사용자 환경을 제공하기 위해 서비스의 응답을 보강 및/또는 다시 포맷하는 방법의 좋은 예입니다.

기본 REST 커넥터 만들기

이제 Web.Contents를 호출하는 새 내보낸 함수를 커넥터에 추가합니다.

그러나 OData 서비스에 대한 웹 요청을 성공적으로 수행하려면 몇 가지 표준 OData 헤더를 설정해야 합니다. 이렇게 하려면 공통 헤더 집합을 커넥터에서 새 변수로 정의합니다.

DefaultRequestHeaders = [
    #"Accept" = "application/json;odata.metadata=minimal",  // column name and values only
    #"OData-MaxVersion" = "4.0"                             // we only support v4
];

함수의 TripPin.Feed 구현을 변경하여 OData.Feed를 사용하는 대신 웹 요청을 수행하고 결과를 JSON 문서로 구문 분석합니다.

TripPinImpl = (url as text) =>
    let
        source = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),
        json = Json.Document(source)
    in
        json;

커넥터 파일을 변경했으므로 이제 커넥터를 빌드해야 합니다. 그런 다음 쿼리 파일(TripPin.query.pq)을 평가할 수 있습니다. 이제 /Me 레코드의 결과는 Fiddler 요청에 있던 원시 JSON과 유사합니다.

새 기능을 실행할 때 Fiddler를 살펴보면, 평가 과정에서 이제 3번이 아닌 단 1번의 웹 요청만 이루어지는 것을 알 수 있습니다. 축하합니다. 300% 성능 향상을 달성했습니다! 이제 모든 형식 및 스키마 정보가 손실되었지만 아직 해당 부분에 집중할 필요가 없습니다.

다음과 같은 TripPin 엔터티/테이블 중 일부에 액세스하도록 쿼리를 업데이트합니다.

  • https://services.odata.org/v4/TripPinService/Airlines
  • https://services.odata.org/v4/TripPinService/Airports
  • https://services.odata.org/v4/TripPinService/Me/Trips

잘 매끄럽게 서식된 테이블을 반환하던 경로들이 이제 [List]가 포함된 최상위 "값" 필드를 반환합니다. 최종 사용자 사용 시나리오에 사용할 수 있도록 결과에 대한 몇 가지 변환을 수행해야 합니다.

포함된 목록이 포함된 값 필드가 있는 PQTest 결과의 스크린샷

파워 쿼리에서 변환 작성

M 변환을 직접 작성할 수 있지만 대부분의 사람들은 파워 쿼리를 사용하여 데이터를 셰이프하는 것을 선호합니다. Power BI Desktop에서 확장을 열고 이를 사용하여 쿼리를 디자인하여 출력을 보다 사용자에게 친숙한 형식으로 전환합니다. 솔루션을 다시 빌드하고, 새 확장 파일을 Custom Data Connectors 디렉터리에 복사하고, Power BI Desktop을 다시 시작합니다.

새 빈 쿼리를 시작하고 다음 줄을 수식 입력줄에 붙여넣습니다.

= TripPin.Feed("https://services.odata.org/v4/TripPinService/Airlines")

= 기호를 포함해야 합니다.

원본 OData 피드처럼 보일 때까지 출력을 조작합니다( AirlineCode 및 Name이라는 두 개의 열이 있는 테이블).

AirlineCode 및 이름 열이 포함된 파워 쿼리 테이블의 스크린샷

결과 쿼리는 다음과 같이 표시됩니다.

let
    Source = TripPin.Feed("https://services.odata.org/v4/TripPinService/Airlines"),
    value = Source[value],
    toTable = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expand = Table.ExpandRecordColumn(toTable, "Column1", {"AirlineCode", "Name"}, {"AirlineCode", "Name"})
in
    expand

쿼리 이름을 지정합니다("Airlines").

새 빈 쿼리를 만듭니다. 이번에는 함수를 TripPin.Feed 사용하여 /Airports 엔터티에 액세스합니다. 다음 표와 비슷한 항목이 표시될 때까지 변환을 적용합니다. 일치하는 쿼리는 테이블을 따릅니다. 이 쿼리의 이름("Airports")도 지정합니다.

변환된 테이블을 포함하는 파워 쿼리 테이블의 스크린샷

let
    Source = TripPin.Feed("https://services.odata.org/v4/TripPinService/Airports"),
    value = Source[value],
    #"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"Name", "IcaoCode", "IataCode", "Location"}, {"Name", "IcaoCode", "IataCode", "Location"}),
    #"Expanded Location" = Table.ExpandRecordColumn(#"Expanded Column1", "Location", {"Address", "Loc", "City"}, {"Address", "Loc", "City"}),
    #"Expanded City" = Table.ExpandRecordColumn(#"Expanded Location", "City", {"Name", "CountryRegion", "Region"}, {"Name.1", "CountryRegion", "Region"}),
    #"Renamed Columns" = Table.RenameColumns(#"Expanded City",{{"Name.1", "City"}}),
    #"Expanded Loc" = Table.ExpandRecordColumn(#"Renamed Columns", "Loc", {"coordinates"}, {"coordinates"}),
    #"Added Custom" = Table.AddColumn(#"Expanded Loc", "Latitude", each [coordinates]{1}),
    #"Added Custom1" = Table.AddColumn(#"Added Custom", "Longitude", each [coordinates]{0}),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom1",{"coordinates"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Columns",{{"Name", type text}, {"IcaoCode", type text}, {"IataCode", type text}, {"Address", type text}, {"City", type text}, {"CountryRegion", type text}, {"Region", type text}, {"Latitude", type number}, {"Longitude", type number}})
in
    #"Changed Type"

서비스에서 더 많은 경로에 대해 이 프로세스를 반복할 수 있습니다. 준비가 되면 (모의) 탐색 테이블을 만드는 다음 단계로 이동합니다.

탐색 테이블 시뮬레이션

이제 M 코드를 사용하여 형식이 제대로 지정된 TripPin 엔터티를 제공하는 테이블을 작성합니다.

새 빈 쿼리를 시작하고 고급 편집기를 표시합니다.

다음 쿼리에 붙여넣습니다.

let
    source = #table({"Name", "Data"}, {
        { "Airlines", Airlines },
        { "Airports", Airports }
    })
in
    source

개인 정보 수준 설정을 "항상 개인 정보 수준 설정 무시"("빠른 결합"이라고도 함)로 설정하지 않은 경우 개인 정보 프롬프트가 표시됩니다.

파워 쿼리 개인 정보 프롬프트의 스크린샷.

여러 원본의 데이터를 결합하고 하나 이상의 원본에 대한 개인 정보 수준을 지정하지 않은 경우 개인 정보 프롬프트가 표시됩니다. 계속 단추를 선택하고 상위 원본의 개인 정보 수준을 공용으로 설정합니다.

상위 원본의 개인 정보 수준을 공개로 설정하는 개인 정보 수준 대화 상자의 스크린샷.

저장을 선택하면 테이블이 나타납니다. 이 테이블은 아직 탐색 테이블은 아니지만 후속 단원에서 이를 하나로 전환하는 데 필요한 기본 기능을 제공합니다.

TripPin 데이터가 포함된 테이블의 스크린샷.

확장 내에서 여러 데이터 원본에 액세스할 때는 데이터 조합 검사가 수행되지 않습니다. 확장 내에서 수행된 모든 데이터 원본 호출은 동일한 권한 부여 컨텍스트를 상속하므로 결합하는 것이 "안전"하다는 가정이 있습니다. 확장은 데이터 조합 규칙과 관련하여 항상 단일 데이터 원본으로 처리됩니다. 원본을 다른 M 원본과 결합할 때 사용자는 여전히 정기적인 개인 정보 보호 프롬프트를 받게 됩니다.

Fiddler를 실행하고 파워 쿼리 편집기에서 미리 보기 새로 고침 단추를 선택하는 경우 탐색 테이블의 각 항목에 대한 별도의 웹 요청을 확인합니다. 이러한 개별 요청은 요소가 많은 탐색 테이블을 빌드할 때 적절하지 않은 조급한 평가가 발생함을 나타냅니다. 후속 단원에서는 게으른 평가를 지원하는 적절한 내비게이션 테이블을 구축하는 방법을 보여 줍니다.

결론

이 단원에서는 REST 서비스에 대한 간단한 커넥터를 빌드하는 방법을 보여 줍니다. 이 경우 기존 OData 확장을 표준 REST 확장( Web.Contents 사용)으로 전환했지만 처음부터 새 확장을 만드는 경우에도 동일한 개념이 적용됩니다.

다음 단원에서는 Power BI Desktop을 사용하여 이 단원에서 만든 쿼리를 확장 내에서 진정한 탐색 테이블로 변환합니다.

다음 단계

TripPin 3부 - 탐색 테이블