Advertisement
Guest User

Untitled

a guest
Jul 16th, 2019
187
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.79 KB | None | 0 0
  1. /**
  2. * ---------------------------
  3. * Domain Layer
  4. * ---------------------------
  5. */
  6. class Order {
  7. /**
  8. *
  9. * @param {Object} prop
  10. * @param {string} prop.id
  11. * @param {string} prop.userId
  12. * @param {string} prop.storeId
  13. * @param {string} prop.orderNo
  14. * @param {Order.STATUS_ENUM} prop.status
  15. * @param {[OrderItem]} prop.items
  16. * @param {string} prop.createdAt
  17. * @param {string} prop.updatedAt
  18. */
  19. constructor({
  20. id,
  21. userId,
  22. storeId,
  23. orderNo,
  24. status,
  25. items,
  26. createdAt,
  27. updatedAt,
  28. }) {
  29. this.id = id;
  30. this.userId = userId;
  31. this.storeId = storeId;
  32. this.orderNo = orderNo;
  33. this.status = status;
  34. this.items = items;
  35. this.createdAt = createdAt;
  36. this.updatedAt = updatedAt;
  37. this._getNowIso = () => new Date().toISOString();
  38. }
  39.  
  40. setGetNowIso(_getNowIso) {
  41. this._getNowIso = _getNowIso;
  42. }
  43.  
  44. cancel() {
  45. if (
  46. this.status === Order.STATUS_ENUM.CANCELED ||
  47. this.status === Order.STATUS_ENUM.CLOSED
  48. ) {
  49. throw new Error();
  50. }
  51. this.status = Order.STATUS_ENUM.CANCELED;
  52. }
  53. close() {
  54. if (
  55. this.status === Order.STATUS_ENUM.CANCELED ||
  56. this.status === Order.STATUS_ENUM.CLOSED
  57. ) {
  58. throw new Error();
  59. }
  60. this.status = Order.STATUS_ENUM.CLOSED;
  61. }
  62.  
  63. static create({ id, userId, storeId, orderNo, items }) {
  64. return new Order({
  65. id,
  66. userId,
  67. storeId,
  68. orderNo,
  69. status: Order.STATUS_ENUM.CREATED, // business rule
  70. items,
  71. createdAt: new Date().toISOString(),
  72. updatedAt: new Date().toISOString(),
  73. });
  74. }
  75. }
  76.  
  77. /** @enum {string} - status of the order */
  78. Order.STATUS_ENUM = {
  79. CREATED: 'CREATED',
  80. CLOSED: 'CLOSED',
  81. CANCELED: 'CANCELED',
  82. };
  83.  
  84. class OrderItem {
  85. /**
  86. * @param {Object} prop
  87. * @param {string} prop.orderId
  88. * @param {string} prop.variantId
  89. * @param {OrderItem.TYPE_ENUM} prop.type
  90. * @param {string} prop.name
  91. * @param {number} prop.price
  92. * @param {number} prop.count
  93. */
  94. constructor({ orderId, variantId, type, name, price, count }) {
  95. if (type === OrderItem.TYPE_ENUM.PRODUCT && price < 0) {
  96. throw new Error();
  97. } else if (type === OrderItem.TYPE_ENUM.GIFT && price !== 0) {
  98. throw new Error();
  99. }
  100. this.orderId = orderId;
  101. this.type = type;
  102. this.variantId = variantId;
  103. this.name = name;
  104. this.price = price;
  105. this.count = count;
  106. }
  107.  
  108. /**
  109. * @returns {number}
  110. */
  111. // @ts-ignore
  112. get total() {
  113. const total = this.total * this.count;
  114. return this.type === OrderItem.TYPE_ENUM.DISCOUNT ? -1 * total : total;
  115. }
  116.  
  117. changePrice(price) {
  118. return new OrderItem({ ...this, price });
  119. }
  120.  
  121. /**
  122. * @param {Object} prop
  123. * @param {string} prop.orderId
  124. * @param {string|null} prop.variantId
  125. * @param {string} prop.name
  126. * @param {number} prop.price
  127. * @param {number} prop.count
  128. */
  129. static createProductTypeItem({ orderId, variantId, name, price, count }) {
  130. return new OrderItem({
  131. orderId,
  132. type: OrderItem.TYPE_ENUM.PRODUCT,
  133. name,
  134. price,
  135. count,
  136. variantId,
  137. });
  138. }
  139.  
  140. /**
  141. * @param {Object} prop
  142. * @param {string} prop.orderId
  143. * @param {string} prop.variantId
  144. * @param {string} prop.name
  145. * @param {number} prop.count
  146. */
  147. static createGiftTypeItem({ orderId, variantId, name, count }) {
  148. return new OrderItem({
  149. orderId,
  150. type: OrderItem.TYPE_ENUM.GIFT,
  151. name,
  152. price: 0,
  153. count,
  154. variantId,
  155. });
  156. }
  157.  
  158. /**
  159. * @param {Object} prop
  160. * @param {string} prop.orderId
  161. * @param {string} prop.name
  162. * @param {number} prop.price
  163. */
  164. static createDiscountTypeItem({ orderId, name, price }) {
  165. return new OrderItem({
  166. orderId,
  167. type: OrderItem.TYPE_ENUM.DISCOUNT,
  168. name,
  169. price,
  170. count: 1,
  171. variantId: null,
  172. });
  173. }
  174.  
  175. /**
  176. * @param {Object} prop
  177. * @param {string} prop.orderId
  178. * @param {string} prop.name
  179. * @param {number} prop.price
  180. */
  181. static createShipmentFeeTypeItem({ orderId, name, price = 0 }) {
  182. return new OrderItem({
  183. orderId,
  184. type: OrderItem.TYPE_ENUM.SHIPMENT_FEE,
  185. name,
  186. price,
  187. count: 1,
  188. variantId: null,
  189. });
  190. }
  191.  
  192. /**
  193. * @param {Object} prop
  194. * @param {string} prop.orderId
  195. * @param {string} prop.name
  196. * @param {number} prop.price
  197. */
  198. static createPaymentFeeTypeItem({ orderId, name, price = 0 }) {
  199. return new OrderItem({
  200. orderId,
  201. type: OrderItem.TYPE_ENUM.PAYMENT_FEE,
  202. name,
  203. price,
  204. count: 1,
  205. variantId: null,
  206. });
  207. }
  208. }
  209.  
  210. /** @enum {string} - type of the order item */
  211. OrderItem.TYPE_ENUM = {
  212. PRODUCT: 'PRODUCT',
  213. DISCOUNT: 'DISCOUNT',
  214. GIFT: 'GIFT',
  215. SHIPMENT_FEE: 'SHIPMENT_FEE',
  216. PAYMENT_FEE: 'PAYMENT_FEE',
  217. };
  218.  
  219. module.exports = {
  220. Order,
  221. OrderItem,
  222. };
  223.  
  224. /**
  225. * ---------------------------
  226. * Domain Layer Testing
  227. * ---------------------------
  228. */
  229. const test = require('ava');
  230. const create_testcases = [];
  231. const create_testMacro = (t, input) => {
  232. Order.create(input);
  233. t.pass();
  234. };
  235. create_testcases.forEach(tc =>
  236. // @ts-ignore
  237. test(tc.description, create_testMacro, tc.input)
  238. );
  239.  
  240. const cancel_testcases = [];
  241. const cancel_testMacro = (t, input) => {
  242. const o = Order.create(input);
  243. o.cancel();
  244. t.is(o.status, Order.STATUS_ENUM.CANCELED);
  245. };
  246. cancel_testcases.forEach(tc =>
  247. // @ts-ignore
  248. test(tc.description, cancel_testMacro, tc.input)
  249. );
  250.  
  251. /**
  252. * ---------------------------
  253. * Repository Layer
  254. * * a repository class
  255. * * a toXXX helper function
  256. * * a toDb helper function
  257. * ---------------------------
  258. */
  259.  
  260. class OrderRepository {
  261. /** * @param {Object} db */
  262. constructor(db) {
  263. this.db = db;
  264. }
  265. /**
  266. * @param {string} id
  267. * @returns {Promise<Order>}
  268. */
  269. async getOrderById(id) {
  270. const dbData = await this.db.query('SELECT data FROM order WHERE id = $1', [
  271. id,
  272. ]);
  273. return toOrder(dbData);
  274. }
  275. /**
  276. * @param {Order} order
  277. * @returns {Promise<Order>}
  278. */
  279. async insertOrder(order) {
  280. const dbData = await this.db.query(
  281. 'INSERT INTO order (data) VALUES ($1) RETURNING data',
  282. [toDb(order)]
  283. );
  284. return toOrder(dbData);
  285. }
  286. /**
  287. * @param {string} id
  288. * @param {Order} order
  289. * @returns {Promise<Order>}
  290. */
  291. async updateOrder(id, order) {
  292. const dbData = await this.db.query(
  293. 'UPDATE order set data = ($1) WHERE id = $2 RETURNING data',
  294. [toDb(order), id]
  295. );
  296. return toOrder(dbData);
  297. }
  298. }
  299.  
  300. /**
  301. * @param {Object} data
  302. * @returns {Order}
  303. */
  304. function toOrder(data) {
  305. return new Order({
  306. id: data.id,
  307. userId: data.userId,
  308. storeId: data.storeId,
  309. orderNo: data.orderNo,
  310. status: data.status,
  311. items: data.items.map(item => new OrderItem(item)),
  312. createdAt: data.createdAt,
  313. updatedAt: data.updatedAt,
  314. });
  315. }
  316.  
  317. /**
  318. * @param {Order} order
  319. * @returns {Object}
  320. */
  321. function toDb(order) {
  322. return { orderNo: order.orderNo, ...order };
  323. }
  324.  
  325. /**
  326. * ---------------------------
  327. * Application Layer
  328. * ---------------------------
  329. */
  330. const uuidV4 = require('uuid/v4');
  331. /**
  332. * Use case1: createOrder
  333. * @param {Object} param
  334. * @param {Object} param.input
  335. * @param {OrderRepository} param.orderRepo
  336. * @returns {Promise<Order>}
  337. */
  338. async function createOrder({ input, orderRepo }) {
  339. const {
  340. userId,
  341. storeId,
  342. orderNo,
  343. products,
  344. payment,
  345. shipment,
  346. discount,
  347. } = input;
  348.  
  349. const orderId = uuidV4();
  350.  
  351. const order = Order.create({
  352. userId,
  353. storeId,
  354. orderNo,
  355. id: orderId,
  356. items: [
  357. OrderItem.createPaymentFeeTypeItem({ orderId, ...payment }),
  358. OrderItem.createShipmentFeeTypeItem({ orderId, ...shipment }),
  359. OrderItem.createDiscountTypeItem({ orderId, ...discount }),
  360. ...products.map(p => OrderItem.createProductTypeItem({ orderId, ...p })),
  361. ],
  362. });
  363.  
  364. await orderRepo.insertOrder(order);
  365. return order;
  366. }
  367.  
  368. /**
  369. * Use case 2: cancel order
  370. * @param {Object} param
  371. * @param {string} param.id
  372. * @param {OrderRepository} param.orderRepo
  373. * @returns {Promise<Order>}
  374. */
  375. async function cancelOrder({ id, orderRepo }) {
  376. const order = await orderRepo.getOrderById(id);
  377. order.cancel();
  378. await orderRepo.updateOrder(id, order);
  379. return order;
  380. }
  381.  
  382. /**
  383. * ---------------------------
  384. * Entry point
  385. * ---------------------------
  386. */
  387. async function entryPoint() {
  388. const input = {
  389. userId: '11111111-1111-1111-1111-111111111111',
  390. storeId: '22222222-2222-2222-2222-222222222222',
  391. orderNo: 'ORDER0001',
  392. products: [
  393. { name: 'A', price: 1000, count: 1, variantId: '1' },
  394. { name: 'B', price: 50, count: 10, variantId: '2' },
  395. ],
  396. payment: { name: 'Credit Card', price: 10 },
  397. shipment: { name: 'CVS', price: 100 },
  398. discount: { name: 'Summer Discount', price: 50 },
  399. };
  400.  
  401. const db = {};
  402. const order = await createOrder({
  403. input,
  404. orderRepo: new OrderRepository(db),
  405. });
  406. await cancelOrder({ id: order.id, orderRepo: new OrderRepository(db) });
  407. }
  408. entryPoint();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement