// // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // .Net StockTrader Sample WCF Application for Benchmarking, Performance Analysis and Design Considerations for Service-Oriented Applications //====================================================================================================== // The implementation class for the BSL, defining all the business logic. //====================================================================================================== //====================================================================================================== // Code originally contributed by Microsoft Corporation. // This contribution to the Stonehenge project is limited strictly // to the source code that is submitted in this submission. // Any technology, including underlying platform technology, // that is referenced or required by the submitted source code // is not a part of the contribution. // For example and not by way of limitation, // any systems/Windows libraries (WPF, WCF, ASP.NET etc.) // required to run the submitted source code is not a part of the contribution //====================================================================================================== using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Transactions; using System.Reflection; using System.Configuration; using System.Security.Policy; using Trade.IDAL; using Trade.DALFactory; using Trade.BusinessServiceConfigurationSettings; using Trade.BusinessServiceDataContract; using Trade.BusinessServiceContract; using Trade.Utility; using Trade.OrderProcessorAsyncClient; using Trade.OrderProcessorContract; namespace Trade.BusinessServiceImplementation { /// /// Tradeservice.cs. This is the base service implementation class--the Business Service Layer or BSL. /// It holds the actual implementation of the business logic for the various /// business service methods or operations, save for the order submit and processing operations for buys and sells /// (these are contained in the separate project/namespace SharedOrderProcessClass). Both the /// ASMX and WCF Service facades flow through this class by exposing a service interface to this logic. /// This class makes calls to the separate, logical data access layer (DAL). It activates the database /// layer via a factory model, loading a specific DAL on demand, vs direcly referencing /// it in the VS project. This helps to further 'decouple' the BSL from the underlying database it operates against. /// public class TradeService : ITradeServices { private IOrder dalOrder; private ICustomer dalCustomer; private IMarketSummary dalMarketSummary; //The following are the core functional service method implementations that //provide for the business functions/back-end processing of the application. //Each has a counterpart service to the Trade 6.1 WebSphere benchmark application. //These methods are exposed as industry-standard Web Services via both //ASMX and Windows Communication Foundation (.NET 3.5) facades. //As such, the J2EE/WebSphere front end can easily be used with the //.NET backend (and vice-versa); simply by configuring the WCF service endpoints. //The more interesting business logic functions are buy, sell and register new //accounts, since these actually have some business logic required, and transactions //involved. //Another important point is the use of a (quite simple, actually) DAL technique that enables //the BSL to be more effectively isolated from the DAL. Read the technical documentation for more //information. The key here is that our DAL overcomes some previous limitations (for example, see PetShop //3.0 or 4.0) for enterprise applications that had multiple DAL method calls within a single business logic //operation yet did not need to or want to invoke a distributed transaction against SQL Server since all operations //would be against a single database resource. Our new DAL accomplishes this without having //to pass transaction objects or connections between layers. At the same time, it enables the //SQL Server 2005 DAL to fully utilize System.Transaction 'lightweight' transaction feature without //extra code. Newer releases of the Oracle Data Provider for .NET (ODP.NET) also support this feature when using //Oracle database with the Trade.DAL.Oracle Data Access Layer. public void emptyMethodAction() { return; } public void isOnline() { return; } /// /// Logs user in/authenticates against StockTrader database. /// /// User id to authenticate. /// Password for authentication public AccountDataModel login(string userid, string password) { Console.WriteLine("Entering Login"); //Create instance of a DAL, which could be designed for any type of DB backend. dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); //As feature of the StockTrader DAL, you will see dal.Open, dal.BeginTransaction, dal.CommitTransaction, //dal.AbortTransaction and dal.Close methods being invoked in the BSL. The pattern within this BSL is: //a) Create an instance of the DAL; //b) Open the DAL; //c) Start a transaction only if necessary (more than one update/insert/delete involved); //d) You get to pick ADO.NET transaction or System.Transactions or ServicedComponent, it will work with // all of the above; StockTrader lets you choose ADO.NET txs or System.Transactions via config. //e) Close the DAL. This releases the DAL's internal connection back to the connection pool. //The implementation "hides" the type of database being used from the BSL, so this BSL will work //with any type of database you create a DAL for, with no changes in the BSL whatsoever. //System.Transactions and SQL Server 2005 and Oracle databases work together //with a new feature called "lightweight transactions"; which means you do not *need* to have the //same performance penalty you got with Serviced Components for always invoking the tx as a full //two-phase operation with DTC logging unless necessary. For now, only SQL Server 2005 and Oracle databases support this feature which //will automatically keep a transaction as a local database transaction and auto-promote to a distributed tx if needed. //If operating against a single connection to SQL 2005 or Oracle, will not promote to a DTC-coordinated tx; and hence be much faster. //On other databases, System.Transactions (and Serviced Components) *always* invoke the DTC, even when //on a single connection to a single database. So our StockTrader DAL is designed to: // 1. Hide DB implementation from BSL so we maintain clean separation of BSL from DAL. // 2. Let you freely call into the DAL from BSL methods as many times as you want *without* // creating new separate DB connections; which would invoke the DTC even on SQL Server 2005. // 3. As a by-product, it also helps you use ADO.NET transactions without worrying about // passing DB connections/transaction objects between tiers; maintaining cleaner separation // of BSL from DAL. If using ADO.NET txs; you can accomplish DB-implementation isolation also with // the Provider Factories introduced with ADO.NET 2.0/.NET 2.0: see for details: // http://msdn2.microsoft.com/en-us/library/ms379620(VS.80).aspx //Note Open() is not really necessary, since the DAL will open a new connection automatically //if it's internal connection is not already open. It's also free to open up more connections, if desired. //We use Open() to stick with a consistent pattern in this application, since the Close() method IS //important. Look for this pattern in all BSL methods below; with a transaction scope defined //only for operations that actually require a transaction per line (c) above. dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING); try { Console.WriteLine("Leaving Login"); return dalCustomer.login(userid, password); } catch { throw; } finally { //Always close the DAL, this releases its primary DB connection. dalCustomer.Close(); } } /// /// Logs a user out--updates logout count. /// /// User id to logout. public void logout(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { dalCustomer.logOutUser(userID); return; } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets account data for a user. Transforms data from DataContract to model UI class for HTML display. /// /// User id to retrieve data for. public AccountDataModel getAccountData(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getCustomerByUserID(userID); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets account profile data for a user. Transforms data from DataContract to model UI class for HTML display. /// /// User id to retrieve data for. public AccountProfileDataModel getAccountProfileData(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getAccountProfileData(userID); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets recent orders for a user. Transforms data from DataContract to model UI class for HTML display. /// /// User id to retrieve data for. public List getOrders(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getOrders(userID, false, Settings.MAX_QUERY_TOP_ORDERS,Settings.MAX_QUERY_ORDERS); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets specific top n orders for a user. Transforms data from DataContract to model UI class for HTML display. /// /// User id to retrieve data for. public List getTopOrders(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getOrders(userID, true, Settings.MAX_QUERY_TOP_ORDERS,Settings.MAX_QUERY_ORDERS); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets any closed orders for a user--orders that have been processed. Also updates status to complete. /// /// User id to retrieve data for. public List getClosedOrders(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getClosedOrders(userID); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets holding data for a user. Transforms data from DataContract to model UI class for HTML display. /// /// User id to retrieve data for. public List getHoldings(string userID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getHoldings(userID); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets a holding for a user. Transforms data from DataContract to model UI class for HTML display. /// /// User id to retrieve data for. /// Holding id to retrieve data for. public HoldingDataModel getHolding(string userID, int holdingID) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.getHolding(userID, holdingID); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Registers/adds new user to database. /// /// User id for account creation/login purposes as specified by user. /// Password as specified by user. /// Name as specified by user. /// Address as specified by user. /// Email as specified by user. /// Credit card number as specified by user. /// Open balance as specified by user. public AccountDataModel register(string userID, string password, string fullname, string address, string email, string creditcard, decimal openBalance) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); //Switch is two let you configure which transaction model you want to benchmark/test. switch (Settings.TRANSACTION_MODEL) { case (StockTraderUtility.TRANSACTION_MODEL_SYSTEMDOTTRANSACTION_TRANSACTION): { System.Transactions.TransactionOptions txOps = new TransactionOptions(); txOps.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; txOps.Timeout = TimeSpan.FromSeconds(15); //Start our System.Transactions tx with the options set above. System.Transactions //will handle rollbacks automatically if there is an exception; note the //difference between the System.Transaction case and the ADO.NET transaction case; //and where the dal.Open() happens (which opens a 'hidden' DB connection in DAL). //System.Transactions will automatically enlist ANY connection //opened within the tx scope in the transaction for you. Since it supports distributed //tx's; it frees you quite a bit, with the caveat of the overhead of doing a distributed //tx when you do not need one. Hence: lightweight System.Transactions with SQL Server 2005 and Oracle; //and our DAL designed to take advantage of this important feature (auto-promote to DTC //only if needed). ADO.NET txs always require an already-open connection before starting a tx. using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, txOps)) { //Now open the connection, after entering tx scope. dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { AccountDataModel newCustomer = addNewRegisteredUser(userID, password, fullname, address, email, creditcard, openBalance); //Scope complete, commit work. tx.Complete(); return newCustomer; } catch { //no rollback needed, infrastructure will never commit without //scope.Complete() and immediately issue rollback on and unhandled //exception. throw; } finally { dalCustomer.Close(); } } } case (StockTraderUtility.TRANSACTION_MODEL_ADONET_TRANSACTION): { //ADO.NET TX case: First you need to open the connecton. dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; //Now you start TX dalCustomer.BeginADOTransaction(); try { AccountDataModel newCustomer = addNewRegisteredUser(userID, password, fullname, address, email, creditcard, openBalance); //done, commit. dalCustomer.CommitADOTransaction(); return newCustomer; } catch { //explicit rollback needed. dalCustomer.RollBackTransaction(); throw; } finally { //ALWAYS call dal.Close is using StockTrader DAL implementation; //this is equivalent to calling Connection.Close() in the DAL -- //but for a generic DB backend as far as the BSL is concerned. dalCustomer.Close(); } } } throw new Exception(Settings.ENABLE_GLOBAL_SYSTEM_DOT_TRANSACTIONS_CONFIGSTRING + ": " + StockTraderUtility.EXCEPTION_MESSAGE_INVALID_TXMMODEL_CONFIG + " Repository ConfigKey table."); } /// /// Adds user account data to Account table and also profile data to AccountProfile table. /// /// /// /// /// /// /// /// /// private AccountDataModel addNewRegisteredUser(string userID, string password, string fullname, string address, string email, string creditcard, decimal openBalance) { AccountProfileDataModel customerprofile = new AccountProfileDataModel(userID, password, fullname, address, email, creditcard); dalCustomer.insertAccountProfile(customerprofile); //Check our acid test conditions here for transactional testing; we want to test part way through //the register operations from the BSL, to make sure database is never left in state with one //insert above going through, and the one below not--the entire BSL operation needs to be //treated as one logical unit of work. Also note the ordering of operations here: //since trying to register a non-unique userid might be something that happens frequently in the real //world, lets do the insert that would fail on this condition first (accountprofile); //rather than wait and do it last. if (customerprofile.userID.Equals(StockTraderUtility.ACID_TEST_USER)) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_REGISTRATION); AccountDataModel customer = new AccountDataModel(0, userID, DateTime.Now, (decimal)openBalance, 0, (decimal)openBalance, DateTime.Now, 0); dalCustomer.insertAccount(customer); return customer; } /// /// Updates account profile data for a user. /// /// Profile data model class with updated info. public AccountProfileDataModel updateAccountProfile(AccountProfileDataModel profileData) { dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL); dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);; try { return dalCustomer.update(profileData); } catch { throw; } finally { dalCustomer.Close(); } } /// /// Gets the current market summary. This results in an expensive DB query in the DAL; hence look to cache data returned for 60 second or so. /// public MarketSummaryDataModelWS getMarketSummary() { dalMarketSummary = Trade.DALFactory.MarketSummary.Create(Settings.DAL); dalMarketSummary.Open(Settings.TRADEDB_SQL_CONN_STRING); try { return dalMarketSummary.getMarketSummaryData(); } catch { throw; } finally { dalMarketSummary.Close(); } } /// /// Gets a single quote based on symbol. /// /// Symbol to get data for. public QuoteDataModel getQuote(string symbol) { dalMarketSummary = Trade.DALFactory.MarketSummary.Create(Settings.DAL); dalMarketSummary.Open(Settings.TRADEDB_SQL_CONN_STRING); try { return dalMarketSummary.getQuote(symbol); } catch { throw; } finally { dalMarketSummary.Close(); } } /// /// Performs a holding sell operation. /// Note orderProcessing mode param is not used by StockTrader or IBM Trade 6.1; instead /// both apps pick this up from the application configuration. /// /// User id to create/submit order for. /// Holding id to sell off. /// Not used, set to zero. public OrderDataModel sell(string userID, int holdingID, int orderProcessingMode) { //In the case of running in 'Sync_InProcess' mode, then the PlaceOrder method //will synchronously invoke the processOrder method as part of this service, and not make an //additional remote service-to-sevice call via WCF. See ProcessOrder.cs. //note, this method always sells entire holding, quantity is not passed in. This is default behavior of WebSphere Trade 6.1 return placeOrder(StockTraderUtility.ORDER_TYPE_SELL, userID, holdingID, null, (double)0); } /// /// Allows user to sell part of a holding vs. all. /// This is added functionality that .NET StockTrader implements to allow selling of partial /// holdings, vs. liquidating the entire holding at once, ala IBM Trade 6.1. This method is /// exposed by StockTrader as a WCF/Web service; but does not exist in IBM Trade 6.1. /// /// User id to submit sell for. /// Holding id to sell. /// Number of shares to sell. public OrderDataModel sellEnhanced(string userID, int holdingID, double quantity) { return placeOrder(StockTraderUtility.ORDER_TYPE_SELL_ENHANCED, userID, holdingID, null, quantity); } /// /// Performs a stock buy operation. /// Note orderProcessing mode param is not used by StockTrader or IBM Trade 6.1; instead /// both apps pick this up from the application configuration. /// /// User id to create/submit order for. /// Stock symbol to buy. /// Shares to buy. ///Not used. public OrderDataModel buy(string userID, string symbol, double quantity, int orderProcessingMode) { return placeOrder(StockTraderUtility.ORDER_TYPE_BUY, userID, 0, symbol, quantity); } /// /// This is the business logic for placing orders, handling txs. /// StockTrader allows the user to select whether to use the new .NET 2.0 System.Transactions /// or use ADO.NET Provider-supplied transactions for order placement (buy, sell); and new user registrations. /// The best choice for performance will vary based on the backend database being used with the /// application. /// /// Buy or sell type. /// User id to create/submit order for. /// Holding id to sell. /// Stock symbol to buy. /// Shares to buy. public OrderDataModel placeOrder(string orderType, string userID, int holdingID, string symbol, double quantity) { OrderDataModel order = null; HoldingDataModel holding = new HoldingDataModel(); dalOrder = Trade.DALFactory.Order.Create(Settings.DAL); switch (Settings.TRANSACTION_MODEL) { case (StockTraderUtility.TRANSACTION_MODEL_SYSTEMDOTTRANSACTION_TRANSACTION): { System.Transactions.TransactionOptions txOps = new TransactionOptions(); txOps.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; txOps.Timeout = TimeSpan.FromSeconds(Settings.SYSTEMDOTTRANSACTION_TIMEOUT); //Start our System.Transactions tx with the options set above. using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, txOps)) { dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); try { //Business Step 1: create the order header. order = createOrder(orderType, userID, holdingID, symbol, quantity, ref holding); //Business Step 2: Determine which order processing mode to use, //and either process order right away (sync) and in-process with //calling ASP.NET Web app; or interface with the //async WCF Order Processing Service (cooler) via a service-to-service call, //distributed/remote. if (Settings.orderMode != StockTraderUtility.ORDER_Sync) { //Fire up our async client; we follow the same model here as with the //StockTrader Web App in that we do not talk 'directly' to the generated proxy //for the service; rather we channel all calls through a single //class that then talks to the service proxy. This will aid in more //easily knowing where communication and proxy logic sits; and make changes to services //you might want to interface with vs. littering proxy calls throughout the //business tier itself. TradeOrderServiceAsyncClient asyncclient = new TradeOrderServiceAsyncClient(Settings.orderMode); asyncclient.processOrderASync(order); } else { processOrderSync(order, holding); } //Commit! tx.Complete(); return order; } //If per chance you are doing step-through debugging through here and are getting a // "TRANSACTION HAS ABORTED" exception and do not know why, //it's quite likely you are hitting the 15-second timeout we set in //ConfigurationSettings for the System.Transaction options so its just doing what we told it to. //Simply adjust timeout higher, recompile if you need to. catch { throw; } finally { dalOrder.Close(); } } } //Repeat for ADO.NET transactions config option case. case (StockTraderUtility.TRANSACTION_MODEL_ADONET_TRANSACTION): { dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING); dalOrder.BeginADOTransaction(); try { //Business Step 1: create the order header. order = createOrder(orderType, userID, holdingID, symbol, quantity, ref holding); //Business Step 2: Determine which order processing mode to use, //and either process order right away (sync); or interface with the //async WCF Order Processing Service (cooler) via a service-to-service call. if (Settings.orderMode != StockTraderUtility.ORDER_Sync) { //Fire up our async client; we follow the same model here as with the //StockTrader Web App in that we do not talk 'directly' to the generated proxy //for the service; rather we channel all calls through a single //class that then talks to the service proxy. This will aid in more //easily knowing where communication and proxy logic sits; and make changes to services //you might want to interface with vs. littering proxy calls throughout the //business tier itself. TradeOrderServiceAsyncClient asyncclient = new TradeOrderServiceAsyncClient(Settings.orderMode); asyncclient.processOrderASync(order); dalOrder.CommitADOTransaction(); } else { processOrderSync(order, holding); } dalOrder.CommitADOTransaction(); return order; } catch { dalOrder.RollBackTransaction(); throw; } finally { dalOrder.Close(); } } } throw new Exception(Settings.ENABLE_GLOBAL_SYSTEM_DOT_TRANSACTIONS_CONFIGSTRING + ": " + StockTraderUtility.EXCEPTION_MESSAGE_INVALID_TXMMODEL_CONFIG + " Repository ConfigKey table."); } /// /// Business logic to create the order header. /// The order header is always created synchronously by Trade; its the actual /// processing of the order that can be done asynchrounously via the WCF service. /// If, however, this service cannot communicate with the async order processor, /// the order header is rolled back out of the database since we are wrapped in a tx here /// either ADO.NET tx or System.TransactionScope as noted above, based on user setting. /// private OrderDataModel createOrder(string orderType, string userID, int holdingID, string symbol, double quantity, ref HoldingDataModel holding) { OrderDataModel order; switch (orderType) { case StockTraderUtility.ORDER_TYPE_SELL: { holding = dalOrder.getHolding(holdingID); if (holding == null) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_INVALID_HOLDING_NOT_FOUND); order = dalOrder.createOrder(userID, holding.quoteID, StockTraderUtility.ORDER_TYPE_SELL, holding.quantity, holdingID); break; } //StockTrader, unlike WebSphere Trade 6.1, allows users to sell part //of a holding, not required to dump all shares at once. This business logic //on the processing side of the pipe is more tricky. Have to check for //conditions like another order coming in and the shares do not exist anymore //in the holding, etc. This is not done here--it is done in the ProcessOrder class. //Here we are merely creatingt he order header as with all orders--note its just the *quantity* variable //that varies with SELL_ENHANCED case vs. SELL. SELL always uses the total //number of shares in the holding to sell ala IBM Trade 6.1; here we have obtained from the //requesting service (for example, the StockTrader Web App), how many shares //the user actually wants to sell. case StockTraderUtility.ORDER_TYPE_SELL_ENHANCED: { holding = dalOrder.getHolding(holdingID); if (holding == null) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_INVALID_HOLDING_NOT_FOUND); //If user requests to sell more shares than in holding, we will just invoke the core //sell operation which liquidates/sells the entire holding. Seems logical--they will //get proper notification based on the client order alert in the Web app how many //were actually sold. if (quantity > holding.quantity) { goto case StockTraderUtility.ORDER_TYPE_SELL; } else { order = dalOrder.createOrder(userID, holding.quoteID, StockTraderUtility.ORDER_TYPE_SELL, quantity, holdingID); break; } } case StockTraderUtility.ORDER_TYPE_BUY: { //Buys are easier business case! Especially when on unlimited margin accounts :-). order = dalOrder.createOrder(userID, symbol, StockTraderUtility.ORDER_TYPE_BUY, quantity, -1); break; } default: throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_BADORDERTYPE); } return order; } /// /// Business logic to synchrounously process the order within BSL layer, used with OrderMode="Sync." /// /// Order information model class with header info. /// Holding model class with holding to sell if applicable. public void processOrderSync(OrderDataModel order, HoldingDataModel holding) { try { decimal total = 0; int holdingid = -1; QuoteDataModel quote = dalOrder.getQuoteForUpdate(order.symbol); //Get the latest trading price--this is the money going into (or out of) the users account. order.price = quote.price; //Calculate order total, and create/update the Holding. Whole holding //sells delete the holding, partials sells update the holding with new amount //(and potentially the order too), and buys create a new holding. if (order.orderType == StockTraderUtility.ORDER_TYPE_BUY) { holdingid = dalOrder.createHolding(order); total = Convert.ToDecimal(order.quantity) * order.price + order.orderFee; } else if (order.orderType == StockTraderUtility.ORDER_TYPE_SELL) { holdingid = sellHolding(order, holding); total = -1 * Convert.ToDecimal(order.quantity) * order.price + order.orderFee; } //Debit/Credit User Account. Note, if we did not want to allow unlimited margin //trading, we would change the ordering a bit and add the biz logic here to make //sure the user has enough money to actually buy the shares they asked for! //Now Update Account Balance. dalOrder.updateAccountBalance(order.accountID, total); //Update the stock trading volume and price in the quote table dalOrder.updateStockPriceVolume(order.quantity, quote); //Now perform our ACID tx test, if requested based on order type and acid stock symbols if (order.symbol.Equals(StockTraderUtility.ACID_TEST_BUY) && order.orderType == StockTraderUtility.ORDER_TYPE_BUY) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_BUY); else if (order.symbol.Equals(StockTraderUtility.ACID_TEST_SELL) && order.orderType == StockTraderUtility.ORDER_TYPE_SELL) throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_SELL); //Finally, close the order order.orderStatus = StockTraderUtility.ORDER_STATUS_CLOSED; order.completionDate = DateTime.Now; order.holdingID = holdingid; dalOrder.closeOrder(order); //Done! return; } catch { throw; } } /// /// Sell the holding. /// /// /// /// int sellHolding(OrderDataModel order, HoldingDataModel holding) { int holdingid = holding.holdingID; //There are three distinct business cases here, each needs different treatment: // a) Quantity requested is less than total shares in the holding -- update holding. // b) Quantity requested is equal to total shares in the holding -- delete holding. // c) Quantity requested is greater than total shares in the holding -- delete holding, update order table. if (order.quantity < holding.quantity) { dalOrder.updateHolding(holdingid, order.quantity); } else if (holding.quantity == order.quantity) { dalOrder.deleteHolding(holdingid); } else //We now need to back-update the order record quantity to reflect //fact not all shares originally requested were sold since the holding //had less shares in it, perhaps due to other orders //placed against that holding that completed before this one. So we will //sell the remaining shares, but need to update the final order to reflect this. if (order.quantity > holding.quantity) { dalOrder.deleteHolding(holdingid); order.quantity = holding.quantity; dalOrder.updateOrder(order); } return holdingid; } } }