【Swift】SalesforceのREST APIを利用してみる

本稿では、主に企業で利用されているであろうSalesforceというシステムから情報を取得したり更新したりするプログラムをC#で書いてみたいと思います。

Salesforceは顧客管理などができるWebシステムです。詳細は「https://www.salesforce.com/jp/」をご覧ください。

REST APIの詳細はこちらからどうぞ。



REST APIでログインを行う

ログインを行うには「https://login.salesforce.com/services/oauth2/token」へアクセスします。

methodはPOSTを指定し必要な認証パラメータを設定します。

grant_type=password
client_id=「コンシューマ鍵」
client_secret=「コンシューマの秘密」
username=「ユーザー名」
password=「パスワード」+「セキュリティートークン」

必要な値はSalesforceへログインして取得できます。

ユーザー名とパスワードはわかると思いますが、コンシューマ鍵、コンシューマの秘密、セキュリティートークンは取得方法がわからない方もいると思います。
これらの取得手順は、こちらの記事で紹介しております。

値が取得できたらログイン処理を実行してみましょう。

import UIKit

class ViewController: UIViewController {
	
	override func viewDidLoad() {
		super.viewDidLoad()
		login()
	}

	func login(){
		
		let url: String = "https://login.salesforce.com/services/oauth2/token"
		//let url: String = "https://test.salesforce.com/services/oauth2/token"//Sandbox環境の場合
		let method: String = "POST"
		let contentType: String = "application/x-www-form-urlencoded; charset=utf-8"
		var sb: String = String();
		sb.append("grant_type=password")
		sb.append("&")
		sb.append("client_id=54LOAKJL+DJALH.0224D.__jSSAFfsfM__oKLJLKFJSLKJkjLKJSdkjklsjlkdfk253Ge5153213faADdegft133")
		sb.append("&")
		sb.append("client_secret=LSFJHLS2313221SAD325FAD21321")
		sb.append("&")
		sb.append("username=test%40mail.co.jp")
		sb.append("&")
		sb.append("password=aaaasdkjLKJHGK23DA2dda")
		let body:Data? = sb.data(using: .utf8)!
		let values = executeRequest(url: url
			, method: method
			, contentType: contentType
			, accessToken: nil
			, body: body)
		
		if ( values.0 != nil && values.0!.count > 0 ) {
			print(String(data:values.0!,encoding: .utf8)!)
		} else {
			print("レスポンスがないよ!!")
		}
	}
	
	func executeRequest(url: String, method: String, contentType: String?, accessToken: String?, body: Data?)->(Data?,URLResponse?,Error?){
		var d: Data? = nil
		var r: URLResponse? = nil
		var e: Error? = nil
		let semaphore = DispatchSemaphore(value: 0)
		var request: URLRequest = URLRequest(url: URL(string: url)!)
		request.httpMethod = method
		if ( contentType != nil ) {
			request.addValue(contentType!, forHTTPHeaderField: "Content-Type")
		}
		if ( accessToken != nil ) {
			request.addValue("Bearer " + accessToken!, forHTTPHeaderField: "Authorization")
		}
		if ( body != nil ) {
			request.httpBody = body
		}
		let session: URLSession = URLSession.shared
		session.dataTask(with: request, completionHandler: { (data, response, error) in
			d = data
			r = response
			e = error
			semaphore.signal()
			
		}).resume()
		_ = semaphore.wait(timeout: .distantFuture)
		return (d, r, e)
	}
}
実行結果コンソール(インデント付与済み)

{ "access_token": "000A000A0a0A0!QAKAJDSKLNDLAS5215153153AD21312A.LKJHSADLKJAL2465461256151111010000254", "instance_url": "https://app.salesforce.com", "id": "https://login.salesforce.com/id/00D0ED/00392HJGJH00XM000000", "token_type": "Bearer", "issued_at": "73268", "signature": "fkhjsakdfl/jksfdljk2/b1olCBVsdsDG+zZC76ad7889DFDJKY=" }

重要なのはaccess_tokenとinstance_urlです。

これらはデータ取得や更新などをする際に必要な値となります。

access_tokenはログインした証となるものでデータ取得などをする際に必要となります。

instance_urlは実際にデータ取得などするときにアクセスするベースURLとなります。

インスタンスID(appの部分)は使用者によって変わると思います。

テストで必要であれば実際に取得できた値をメモっておきましょう。

この記事では、以降この結果をキーに設定したサンプルを掲載していきます。

REST APIでレコードを作成する

レコードを作成するには
「https://<インスタンスID>.salesforce.com/services/data/<APIバージョン>/sobjects/<オブジェクト名>」
にアクセスし、POSTを指定してリクエストします。

登録内容はJSONデータなどで指定します。

それでは取引先にレコード追加してみましょう。

取引先のオブジェクト名はAccountです。

import UIKit

class ViewController: UIViewController {
	
	override func viewDidLoad() {
		super.viewDidLoad()
		create()
	}
	
	func create(){
		let url: String = "https://app.salesforce.com/services/data/v44.0/sobjects/Account"
		let method: String = "POST"
		let contentType: String = "application/json; charset=utf-8"
		let accessToken: String = "000A000A0a0A0!QAKAJDSKLNDLAS5215153153AD21312A.LKJHSADLKJAL2465461256151111010000254"
		let body: String = "{\"Name\":\"Test\"}"
		let values = executeRequest(url: url
			, method: method
			, contentType: contentType
			, accessToken: accessToken
			, body: body.data(using: .utf8))
		
		if ( values.0 != nil && values.0!.count > 0 ) {
			print(String(data:values.0!,encoding: .utf8)!)
		} else {
			print("レスポンスがないよ!!")
		}
	}
	
	func executeRequest(url: String, method: String, contentType: String?, accessToken: String?, body: Data?)->(Data?,URLResponse?,Error?){
		var d: Data? = nil
		var r: URLResponse? = nil
		var e: Error? = nil
		let semaphore = DispatchSemaphore(value: 0)
		var request: URLRequest = URLRequest(url: URL(string: url)!)
		request.httpMethod = method
		if ( contentType != nil ) {
			request.addValue(contentType!, forHTTPHeaderField: "Content-Type")
		}
		if ( accessToken != nil ) {
			request.addValue("Bearer " + accessToken!, forHTTPHeaderField: "Authorization")
		}
		if ( body != nil ) {
			request.httpBody = body
		}
		let session: URLSession = URLSession.shared
		session.dataTask(with: request, completionHandler: { (data, response, error) in
			d = data
			r = response
			e = error
			semaphore.signal()
			
		}).resume()
		_ = semaphore.wait(timeout: .distantFuture)
		return (d, r, e)
	}
}
実行結果

実行結果コンソール(インデント付与済み)

{ "id": "0011903JO01", "success": true, "errors": [] }

取引先にTestが追加されていれば成功です。

設定できるフィールドはName以外にも多数あります。
指定フィールドはAPIでも取得することはできますし、SOAP APIのレファレンスとかも見てみると案外取れたりします。

idはデータ取得、更新、削除などで使用するキーとなります。

テストで必要であれば実際の値をメモっておきましょう。この記事では以降、この結果をキーに設定したサンプルを掲載していきます。

REST APIでレコードを取得する

レコードを取得するには
「https://<インスタンスID>.salesforce.com/services/data/<APIバージョン>/sobjects/<オブジェクト名>/<オブジェクトID>」
にアクセスし、GETを指定してリクエストします。

先ほど作成したTestレコードのidを設定してデータ取得してみましょう。

import UIKit

class ViewController: UIViewController {
	
	override func viewDidLoad() {
		super.viewDidLoad()
		get()
	}

	func get(){
		
		let url: String = "https://app.salesforce.com/services/data/v44.0/sobjects/Account/0011903JO01"
		let method: String = "GET"
		let contentType: String = "application/json; charset=utf-8"
		let accessToken: String = "000A000A0a0A0!QAKAJDSKLNDLAS5215153153AD21312A.LKJHSADLKJAL2465461256151111010000254"
		let values = executeRequest(url: url
			, method: method
			, contentType: contentType
			, accessToken: accessToken
			, body: nil)
		
		if ( values.0 != nil && values.0!.count > 0 ) {
			print(String(data:values.0!,encoding: .utf8)!)
		} else {
			print("レスポンスがないよ!!")
		}
	}
	
	func executeRequest(url: String, method: String, contentType: String?, accessToken: String?, body: Data?)->(Data?,URLResponse?,Error?){
		var d: Data? = nil
		var r: URLResponse? = nil
		var e: Error? = nil
		let semaphore = DispatchSemaphore(value: 0)
		var request: URLRequest = URLRequest(url: URL(string: url)!)
		request.httpMethod = method
		if ( contentType != nil ) {
			request.addValue(contentType!, forHTTPHeaderField: "Content-Type")
		}
		if ( accessToken != nil ) {
			request.addValue("Bearer " + accessToken!, forHTTPHeaderField: "Authorization")
		}
		if ( body != nil ) {
			request.httpBody = body
		}
		let session: URLSession = URLSession.shared
		session.dataTask(with: request, completionHandler: { (data, response, error) in
			d = data
			r = response
			e = error
			semaphore.signal()
			
		}).resume()
		_ = semaphore.wait(timeout: .distantFuture)
		return (d, r, e)
	}
}
実行結果コンソール(インデント付与済み)

{ "attributes": { "type": "Account", "url": "/services/data/v44.0/sobjects/Account/0011903JO01" }, "Id": "0011903JO01", "IsDeleted": false, "MasterRecordId": null, "Name": "Test", "Type": null, "ParentId": null, "BillingStreet": null, "BillingCity": null, "BillingState": null, "BillingPostalCode": null, "BillingCountry": null, "BillingLatitude": null, "BillingLongitude": null, "BillingGeocodeAccuracy": null, "BillingAddress": null, "ShippingStreet": null, "ShippingCity": null, "ShippingState": null, "ShippingPostalCode": null, "ShippingCountry": null, "ShippingLatitude": null, "ShippingLongitude": null, "ShippingGeocodeAccuracy": null, "ShippingAddress": null, "Phone": null, "Fax": null, "Website": null, "PhotoUrl": "/services/images/photo/0011903JO01", "Industry": null, "NumberOfEmployees": null, "Description": null, "Site": null, "OwnerId": "039012930", "CreatedDate": "2019-09-29T11:03:41.000+0000", "CreatedById": "039012930", "LastModifiedDate": "2019-09-29T11:03:41.000+0000", "LastModifiedById": "039012930", "SystemModstamp": "2019-09-29T11:03:41.000+0000", "LastActivityDate": null, "LastViewedDate": "2019-09-29T11:04:37.000+0000", "LastReferencedDate": "2019-09-29T11:04:37.000+0000", "Jigsaw": null, "JigsawCompanyId": null, "AccountSource": null, "SicDesc": null, }

ほとんど値を設定していないのでnullですね。

REST APIでレコードを更新する

レコードの更新を行うには
「https://<インスタンスID>.salesforce.com/services/data/<APIバージョン>/sobjects/<オブジェクト名>/<オブジェクトID>」
にアクセスし、PATCHを指定してリクエストします。

Java11以前の標準HTTPリクエスト機能はPATCHが使えなくなっていますので、リフレクションで無理やり使えるようにしてみました。

Java11をご利用の方は新しいほうのAPIを利用することをお勧めします。

更新内容はJSON文字列で指定します。

試しに作成したTestのレコードをTest2という名前に変更してみましょう。

import UIKit

class ViewController: UIViewController {
	
	override func viewDidLoad() {
		super.viewDidLoad()
		update()
	}
	
	func update(){
		
		let url: String = "https://app.salesforce.com/services/data/v44.0/sobjects/Account/0011903JO01"
		let method: String = "PATCH"
		let contentType: String = "application/json; charset=utf-8"
		let accessToken: String = "000A000A0a0A0!QAKAJDSKLNDLAS5215153153AD21312A.LKJHSADLKJAL2465461256151111010000254"
		let body: String = "{\"Name\":\"Test2\"}"
		let values = executeRequest(url: url
			, method: method
			, contentType: contentType
			, accessToken: accessToken
			, body: body.data(using: .utf8))
		
		if ( values.0 != nil && values.0!.count > 0 ) {
			print(String(data:values.0!,encoding: .utf8)!)
		} else {
			print("レスポンスがないよ!!")
		}
	}
	
	func executeRequest(url: String, method: String, contentType: String?, accessToken: String?, body: Data?)->(Data?,URLResponse?,Error?){
		var d: Data? = nil
		var r: URLResponse? = nil
		var e: Error? = nil
		let semaphore = DispatchSemaphore(value: 0)
		var request: URLRequest = URLRequest(url: URL(string: url)!)
		request.httpMethod = method
		if ( contentType != nil ) {
			request.addValue(contentType!, forHTTPHeaderField: "Content-Type")
		}
		if ( accessToken != nil ) {
			request.addValue("Bearer " + accessToken!, forHTTPHeaderField: "Authorization")
		}
		if ( body != nil ) {
			request.httpBody = body
		}
		let session: URLSession = URLSession.shared
		session.dataTask(with: request, completionHandler: { (data, response, error) in
			d = data
			r = response
			e = error
			semaphore.signal()
			
		}).resume()
		_ = semaphore.wait(timeout: .distantFuture)
		return (d, r, e)
	}
}
実行結果

取引先名がTest2に変更されればOKです。

実行結果コンソール

レスポンスがないよ!!

REST APIでレコードを削除する

レコードの削除を行うには
「https://<インスタンスID>.salesforce.com/services/data/<APIバージョン>/sobjects/<オブジェクト名>/<オブジェクトID>」
にアクセスし、DELETE を指定してリクエストします。

それでは、先ほど更新したTest2のレコードを削除してみましょう。

import UIKit

class ViewController: UIViewController {
	
	override func viewDidLoad() {
		super.viewDidLoad()
		delete()
	}

	func delete(){
		
		let url: String = "https://app.salesforce.com/services/data/v44.0/sobjects/Account/0011903JO01"
		let method: String = "DELETE"
		let accessToken: String = "000A000A0a0A0!QAKAJDSKLNDLAS5215153153AD21312A.LKJHSADLKJAL2465461256151111010000254"
		let values = executeRequest(url: url
			, method: method
			, contentType: nil
			, accessToken: accessToken
			, body: nil
		)
		
		if ( values.0 != nil && values.0!.count > 0 ) {
			print(String(data:values.0!,encoding: .utf8)!)
		} else {
			print("レスポンスがないよ!!")
		}
	}
	
	func executeRequest(url: String, method: String, contentType: String?, accessToken: String?, body: Data?)->(Data?,URLResponse?,Error?){
		var d: Data? = nil
		var r: URLResponse? = nil
		var e: Error? = nil
		let semaphore = DispatchSemaphore(value: 0)
		var request: URLRequest = URLRequest(url: URL(string: url)!)
		request.httpMethod = method
		if ( contentType != nil ) {
			request.addValue(contentType!, forHTTPHeaderField: "Content-Type")
		}
		if ( accessToken != nil ) {
			request.addValue("Bearer " + accessToken!, forHTTPHeaderField: "Authorization")
		}
		if ( body != nil ) {
			request.httpBody = body
		}
		let session: URLSession = URLSession.shared
		session.dataTask(with: request, completionHandler: { (data, response, error) in
			d = data
			r = response
			e = error
			semaphore.signal()
			
		}).resume()
		_ = semaphore.wait(timeout: .distantFuture)
		return (d, r, e)
	}
}
実行結果

実行結果コンソール

レスポンスがないよ!!

取引先からTest2が削除されたらOKです。