EP 2 : การ Integration ร่วมกับ Microsoft Dynamics 365 for Finance and Operations
สำหรับนักพัฒนาแล้ว คงหนีไม่พ้นต้องเจอการทำ Integration Apps บล็อกนี้จึงมาแนะนำการ integrate หรือสร้าง api สำหรับเชื่อมต่อกับ Microsoft Dynamics 365 for Finance and Operations (ซึ่ง 2 วิธีการที่จะแนะนำในวันนี้ ใช้บ่อยมาก)
1. OData Service
OData เป็นโปรโตคอลมาตรฐานสำหรับการดึงข้อมูล ที่สามารถเข้าถึงได้ด้วยเทคโนโลยี HTTP และ JSON, OData service จะถูก public และใช้งานได้ทันที เมื่อเรา build application ผ่านเงื่อนไข Data Entities ที่ IsPublic = Yes
วิธีการใช้งานคือ http://[baseURI]/data , รายการที่แสดงขึ้นมาทั้งหมด คือ data entities ที่เราสามารถเรียกใช้งานได้ เช่น หากเราต้องการข้อมูลลูกค้า ก็ / ต่อท้ายได้เลย http://[baseURI]/data/Customers
รองรับคำสั่งตาม OData standards เช่น
- ดูข้อมูลทุก Company ในระบบ
http://[baseURI]/data/Customers?cross-company=true
- ดูข้อมูลเฉพาะ Company ทีเราสนใจ
http://[baseURI]/data/Customers?$filter=dataAreaId eq ‘thmf’&cross-company=true
- กรองข้อมูลฟิลอื่นๆก็ได้เหมือนกัน (กลุ่มลูกค้ารหัส 20)
http://[baseURI]/data/Customers?$filter=dataAreaId eq ‘thmf’ and CustomerGroupId eq ‘20’&cross-company=true
และเชื่อมต่อกับตัวดึงข้อมูล OData ใน Power BI Desktop ได้อีกด้วย สะดวกมั่กๆ
… เกือบลืมไปอย่าง แวะมาดูการยืนยันตัวตนในการร้องขอใช้ API กันซักนิด
Authentication
OData Services, JSON-based Custom Service, and REST Metadata Service รองรับการยืนยันตัวตนแบบ OAuth 2.0 ดังนั้นเราต้องมาเตรียมตัวกันก่อน
OAuth — Authorization Code Grant Flow
จาก Flow เราต้องทำการ Register Application ที่ Microsoft Azure Active Directory (AAD) ก่อน เพื่อให้ได้ Authorization token มา (Link)
การ request ทุกครั้งจำเป็นต้อง call 2 ครั้ง ครั้งแรกนำ Authorization token ร้องขอไปยัง Azure AD เพื่อให้ได้ Access token มา (ซึ่งมีเวลาหมดอายุ), ครั้งที่สอง นำ Access token ที่ได้มาแนบไปกับการ request API ที่เราต้องการใช้งาน
ลองทดสอบโดยใช้ Postman
Request 1 : Access Token
POST : https://login.microsoftonline.com/:tenant_id/oauth2/token
Params
tenant_id:c4dc70b8–32c4–4443-afde-530521xxxxxx (ได้จาก AAD)
Body
grant_type:client_credentials
client_id:c8907ca6–09a6–40a6–83ce-e67a4xxxxx (ได้จาก AAD)
client_secret:.Ew2C94l?2elh/rLDszhHFFxxxxxxx (ได้จาก AAD)
resource:[baseURI]
Request 2 : Odata
GET : https://[baseURI]/data/Customers?$filter=dataAreaId eq ‘thmf’&$select=CustomerAccount,Name,AddressDescription,FullPrimaryAddress&cross-company=true
Headers
Authorization:Bearer xxxxxxxxx (จาก access_token Request 1)
Content-Type:application/json
Tests
var json = JSON.parse(responseBody);
tests[“Get customer info”] = !json.error && responseBody !== ‘’ && responseBody !== ‘{}’;
- ข้อจำกัดของ api คือ URL ที่อยู่ใน VM จะไม่สามารถทดสอบได้ ต้องเป็น URL on-cloud หรือ on premises เท่านั้น
2. JSON-based custom service
เป็น custom service ที่เราสามารถสร้างเองได้จาก X++ classes หรือการเขียนโปรแกรมนั่นเอง มีการรับค่าและรีเทิร์นค่าแบบ JSON (JavaScript Object Notation), ทุก service ที่ถูก custom ขึ้นมา จะถูก deploy เป็น 2 endpoint เสมอ
- SOAP endpoint
https://<baseurl>/soap/services/UserSessionService?wsdl
- JSON endpoint
https://<baseurl>/api/services/UserSessionService/AifUserSessionService/GetUserSessionInfo
ตัวอย่างในวันนี้จะเป็นการสร้าง api อย่างง่ายในการเปิดใบสั่งขาย หรือ sales order มาเริ่มกันเลย (coding หน่อยๆ)
ส่วนประกอบของ Object (แอบ Tie-in เบาๆ อิอิ)
Class contract สำหรับดูแลเรื่อง parameter ซึ่งในที่นี้เราให้เป็น List ของรายการสินค้าและจำนวน
[DataContractAttribute]public class QERP_SalesOrderContract{ ItemId itemId; Qty qty; [DataMemberAttribute] public ItemId itemId(ItemId _itemId = itemId) { itemId = _itemId; return itemId; } [DataMemberAttribute] public Qty qty(Qty _qty = qty) { qty = _qty; return qty; }}
Class info สำหรับ return สถานะและรายละเอียด
[DataContractAttribute]public class QERP_SalesOrderInfo{ boolean success; str message; [DataMemberAttribute] public boolean success(boolean _success = success) { success = _success; return success; } [DataMemberAttribute] public str message(str _message = message) { message = _message; return message; }}
Class service สำหรับสร้าง sales order
public class QERP_SalesOrderService{[AifCollectionTypeAttribute('salesLineList', Types::Class, classStr(QERP_SalesOrderContract)), SysEntryPointAttribute(true)]public QERP_SalesOrderInfo CreateSalesOrder(DataAreaId dataArea, CustAccount customer, List salesLineList){ QERP_SalesOrderInfo messageInfo = new QERP_SalesOrderInfo(); QERP_SalesOrderContract salesLineData; SalesTable salesTable; SalesLine salesLine; NumberSeq numberSeq; ListIterator literatorset; ttsbegin; changecompany(dataArea) { numberSeq = NumberSeq::newGetNum(SalesParameters::numRefSalesId()); salesTable.SalesId = numberSeq.num(); salesTable.initValue(); salesTable.CustAccount = customer; salesTable.initFromCustTable(); salesTable.insert(); literatorset = new ListIterator(salesLineList); while (literatorset.more()) { salesLineData = literatorset.value(); salesLine.clear(); salesLine.SalesId = salesTable.SalesId; salesLine.ItemId = salesLineData.itemId(); salesLine.SalesQty = salesLineData.qty(); salesLine.createLine(NoYes::Yes, // Validate NoYes::Yes, // initFromSalesTable NoYes::Yes, // initFromInventTable NoYes::Yes, // calcInventQty NoYes::No, // searchMarkup NoYes::No); // searchPrice literatorset.next(); } } ttscommit; messageInfo.success(true); messageInfo.message(strFmt("Sales order '%1' has been created.", salesTable.SalesId)); return messageInfo; }}
เมื่อ Build application เราก็จะได้ API เรียบร้อย
ทดสอบ request
ใช้ tools ประเภท SoapUI ก็ได้เหมือนกันนะ
จบ…