goldprice.dev และ exchangerate.dev เป็นผลิตภัณฑ์พี่น้องที่ดำเนินการโดยบริษัทเดียวกัน ในระบบใช้งานจริง goldprice.dev เป็นผู้รับผิดชอบสัญญาราคาทองคำ ส่วน exchangerate.dev จัดหาส่วนของอัตราแลกเปลี่ยนสำหรับสกุลเงินลอยตัวที่รองรับ ขอบเขตนี้มีความสำคัญ: ราคาทองคำในสกุลท้องถิ่นประกอบด้วยค่าสังเกตตลาดสองจุด และความสดใหม่ของค่าหนึ่งไม่สามารถทำให้อีกค่าหนึ่งสดใหม่ตามไปด้วย
- goldprice.dev เป็นผู้รับผิดชอบราคาทองคำและการตอบกลับการแปลงค่าแบบสาธารณะ ส่วน exchangerate.dev จัดหาส่วนของอัตราแลกเปลี่ยน
- endpoint สาธารณะ
/v1/convertของ goldprice.dev คือเส้นทางใช้งานจริงแบบเรียกครั้งเดียว - นักพัฒนาสามารถเรียก API ทั้งสองแยกกันได้ เมื่อต้องการที่มาของข้อมูลที่ตรวจสอบย้อนกลับได้
- จัดเก็บ
computed_atของทองคำ และdata_updated_at,timestamp,source,market_sessionของอัตราแลกเปลี่ยนแยกจากกัน - ราคาที่แปลงค่าแล้วเป็นมูลค่าตลาดเชิงบ่งชี้ ไม่ใช่ราคาสำหรับการซื้อขายจริงจากผู้ค้าหรือราคาชำระบัญชี
ขอบเขตของระบบใช้งานจริง
goldprice.dev เริ่มต้นด้วยค่าสังเกตราคาโลหะในสกุลดอลลาร์สหรัฐ ส่วน exchangerate.dev ให้ค่าสังเกตอัตราแลกเปลี่ยนจากดอลลาร์สหรัฐไปยังสกุลเงินท้องถิ่นที่สอดคล้องกันสำหรับสกุลเงินลอยตัวที่รองรับ จากนั้น goldprice.dev จะเป็นผู้รับผิดชอบสัญญาการแปลงค่าเฉพาะโลหะ ซึ่งรวมถึงหน่วย สัญลักษณ์ที่รองรับ การตรวจสอบความถูกต้อง และการตอบกลับขั้นสุดท้าย
นี่คือการผสานระบบระหว่างผลิตภัณฑ์พี่น้องที่เปิดเผยอย่างชัดเจน ไม่ใช่การรับรองจากลูกค้าอิสระ คุณค่าของกรณีศึกษานี้อยู่ที่การใช้งานจริง: API อัตราแลกเปลี่ยนจัดหาส่วนข้อมูลใช้งานจริงส่วนหนึ่ง และผลิตภัณฑ์ที่นำไปใช้ยังคงรับผิดชอบต่อขอบเขตงานของตัวเองอยู่
สูตรการแปลงค่า
ทองคำมักถูกเสนอราคาเป็นดอลลาร์สหรัฐต่อทรอยออนซ์ ขณะที่การแสดงผลในสกุลท้องถิ่นมักต้องการจำนวนเงินต่อกรัม ให้แปลงหน่วยน้ำหนักก่อน แล้วจึงคูณด้วยอัตราแลกเปลี่ยนดอลลาร์สหรัฐ:
const GRAMS_PER_TROY_OZ = 31.1035;function localGoldPerGram(usdPerTroyOz, usdToLocalRate) { return (usdPerTroyOz / GRAMS_PER_TROY_OZ) * usdToLocalRate;}ค่าคงที่ของหน่วยน้ำหนักนั้นตายตัว แต่ข้อมูลนำเข้าจากตลาดทั้งสองไม่ใช่: ทั้งราคาทองคำและอัตราแลกเปลี่ยนต่างมีเวลาสังเกตและกฎความสดใหม่เป็นของตัวเอง
เส้นทางใช้งานจริงแบบเรียกครั้งเดียว
สำหรับผู้เรียกใช้งาน ขอบเขตของระบบใช้งานจริงคือ goldprice.dev `/v1/convert` ซึ่งรวมส่วนของโลหะและอัตราแลกเปลี่ยนไว้เบื้องหลัง endpoint เดียวที่รู้จักโลหะ และไม่ต้องใช้ API key ตัวอย่างนี้ร้องขอมูลค่าของทองคำหนึ่งกรัมเป็นสกุลเงินรูเปียห์อินโดนีเซีย:
const params = new URLSearchParams({ from: "XAU", to: "IDR", amount: "1", unit: "gram",});const response = await fetch( `https://api.goldprice.dev/v1/convert?${params}`,);if (!response.ok) { throw new Error(`goldprice.dev returned ${response.status}`);}const conversion = await response.json();console.log(conversion.result, conversion.to, conversion.timestamp);เส้นทางนี้ทำให้สัญญาของผู้เรียกใช้งานเรียบง่าย: หนึ่งคำขอ หนึ่งจำนวน หนึ่งหน่วยโลหะ และสกุลเงินปลายทางหนึ่งสกุล ค่า timestamp เพียงค่าเดียวไม่ได้เป็นหลักฐานยืนยันความสดใหม่ของทั้งสองส่วนตลาดแยกจากกัน ดังนั้นผู้ใช้งานที่ต้องตรวจสอบความสดใหม่ของอัตราแลกเปลี่ยนควรใช้เส้นทางแบบสองส่วนด้านล่างแทน
การผสานระบบแบบสองส่วนที่ตรวจสอบย้อนกลับได้
เรียก API ทั้งสองตัวเมื่อคุณต้องการเก็บ timestamp ของแต่ละแหล่งข้อมูล แคชค่านำเข้าแยกจากกัน หรืออธิบายได้ว่าส่วนใดที่ข้อมูลเก่าไป goldprice.dev spot จัดหาราคาทองคำดอลลาร์สหรัฐต่อทรอยออนซ์ ส่วน exchangerate.dev จัดหาอัตราแลกเปลี่ยนดอลลาร์สหรัฐไปยังสกุลเงินท้องถิ่น
const GRAMS_PER_TROY_OZ = 31.1035;async function getGoldInCurrencies(currencies) { const symbols = currencies.join(","); const [spotResponse, fxResponse] = await Promise.all([ fetch("https://api.goldprice.dev/v1/spot/XAU-USD-SPOT"), fetch(`https://api.exchangerate.dev/v1/latest/USD?symbols=${symbols}`), ]); if (!spotResponse.ok || !fxResponse.ok) { throw new Error("A market-data request failed"); } const spot = await spotResponse.json(); const fx = await fxResponse.json(); const usdPerGram = Number(spot.price) / GRAMS_PER_TROY_OZ; return { goldComputedAt: spot.computed_at, fxDataUpdatedAt: fx.data_updated_at, fxResponseTimestamp: fx.timestamp, fxSource: fx.source, fxMarketSession: fx.market_session, prices: Object.fromEntries( currencies.map((currency) => [ currency, usdPerGram * Number(fx.rates[currency]), ]), ), };}const result = await getGoldInCurrencies(["EUR", "IDR", "JPY"]);เก็บรักษา spot.computed_at ไว้สำหรับค่าสังเกตราคาทองคำ และ fx.data_updated_at สำหรับค่าสังเกตอัตราแลกเปลี่ยนที่เก่าที่สุดที่มีส่วนร่วม ค่า timestamp ของอัตราแลกเปลี่ยนคือเวลาที่สร้างการตอบกลับ ส่วน source และ market_session เพิ่มบริบทของประเภทข้อมูลและช่วงเวลาซื้อขาย อย่าแทนที่ฟิลด์เหล่านี้ด้วย Date.now()
เหตุใดทั้งสองบริการจึงยังคงแยกจากกัน
การผสานระบบนี้ไม่ได้ทำให้ exchangerate.dev กลายเป็นเอนจินราคาทองคำทั้งหมด แต่ยังคงเป็นเพียงชั้นข้อมูลอัตราแลกเปลี่ยน การรักษาขอบเขตนี้ให้ชัดเจนช่วยป้องกันไม่ให้ความสดใหม่ของสกุลเงิน ความสดใหม่ของโลหะ และกฎการแปลงค่าเฉพาะผลิตภัณฑ์ ถูกรวมกันเป็นค่าเดียวที่กำกวม
- goldprice.dev สามารถเปิดให้ใช้งานอินเทอร์เฟซขนาดเล็กเฉพาะโลหะ โดยไม่ต้องให้ผู้เรียกใช้งานประกอบเส้นทางทั่วไปด้วยตัวเอง
- exchangerate.dev ยังคงรักษาสัญญาอัตราแลกเปลี่ยนทั่วไปที่มีประโยชน์นอกเหนือขอบเขตของโลหะ
- ผู้ใช้งานที่ต้องการตรวจสอบย้อนกลับสามารถดึงและจัดเก็บค่าสังเกตทั้งสองแยกจากกันได้
- แต่ละบริการสามารถรายงานความล้มเหลวและความสดใหม่ โดยใช้คำศัพท์เฉพาะของข้อมูลตลาดของตัวเอง
จัดการส่วนข้อมูลที่เก่าหรือขาดหายไปอย่างชัดเจน
ราคาที่รวมกันแล้วไม่ควรถูกนำไปใช้ต่อ หากข้อมูลนำเข้าฝั่งใดฝั่งหนึ่งขาดหายไป เป็นศูนย์ ไม่ใช่ตัวเลข หรืออยู่นอกช่วงความสดใหม่ที่ผลิตภัณฑ์ของคุณยอมรับ เก็บค่าที่ถูกต้องล่าสุดไว้ได้ก็ต่อเมื่ออินเทอร์เฟซระบุเวลาสังเกตดั้งเดิมกำกับไว้ด้วยเท่านั้น
- ตรวจสอบการตอบกลับ HTTP ทั้งสองรายการก่อนทำการคำนวณ
- ปฏิเสธราคาและอัตราที่ไม่ใช่จำนวนจำกัดหรือไม่เป็นบวก
- กำหนดเกณฑ์ความสดใหม่แยกกันสำหรับทองคำและอัตราแลกเปลี่ยน
- พิจารณา
market_session: weekendเป็นเพียงบริบท ไม่ใช่หลักฐานว่าอัตราที่ยังคงค้างอยู่นั้นเป็นข้อมูลปัจจุบัน - บันทึกคู่สกุลเงินและเวลาสังเกตของข้อมูลต้นทางทั้งสองไว้พร้อมกับข้อผิดพลาดในการแปลงค่า
ปัดเศษเพื่อการแสดงผล ไม่ใช่ระหว่างการคำนวณ
รักษาความละเอียดเต็มรูปแบบตลอดการคูณ และปล่อยให้ locale ของการแสดงผลเป็นตัวกำหนดจำนวนตำแหน่งทศนิยมสุดท้าย Intl.NumberFormat จัดการสัญลักษณ์สกุลเงินและการจัดกลุ่มตัวเลขโดยไม่เปลี่ยนแปลงค่าที่แท้จริง:
function formatPrice(amount, currency, locale) { return new Intl.NumberFormat(locale, { style: "currency", currency, }).format(amount);}console.log(formatPrice(result.prices.IDR, "IDR", "id-ID"));console.log(formatPrice(result.prices.JPY, "JPY", "ja-JP"));ใช้ผลลัพธ์เป็นมูลค่าเชิงบ่งชี้
มูลค่าตลาดที่คำนวณได้ไม่ใช่ราคาขายปลีกของเหรียญหรือทองคำแท่ง ค่าพรีเมียมจากผู้ค้า ภาษี ค่าผลิต ค่าธรรมเนียมการชำระเงิน และส่วนต่างอัตราแลกเปลี่ยนสำหรับการซื้อขายจริง ล้วนอยู่นอกเหนือสูตรนี้ ให้ระบุผลลัพธ์ว่าเป็นค่าเชิงบ่งชี้ เว้นแต่แอปพลิเคชันของคุณจะเพิ่มราคาจากผู้ค้าจริงพร้อมเงื่อนไขทางการค้าเข้าไปด้วย
สิ่งที่กรณีศึกษานี้พิสูจน์
goldprice.dev คือหลักฐานจากฝ่ายแรกที่แสดงว่า exchangerate.dev สามารถจัดหาส่วนของอัตราแลกเปลี่ยนให้กับเวิร์กโฟลว์การตั้งราคาในระบบใช้งานจริงได้ นี่ไม่ใช่หลักฐานทางสังคมที่เป็นอิสระ หลักฐานที่มีประโยชน์จริงนั้นแคบกว่าและทำซ้ำได้: นักพัฒนาสามารถเรียกใช้ผลิตภัณฑ์สาธารณะชุดเดียวกัน เก็บรักษาเวลาสังเกตทั้งสองไว้ และตรวจสอบการคำนวณด้วยตัวเองได้