Re: Keeping Actions clean - separating actions from business model from persistence
First, I'd suggest studying the JPetstore3 application. http://www.ibatis.com/jpetstore/jpetstore.html It demonstrates a clean separate of concerns between the logical layers of an application. It also uses a DAO framework and shows how you can you easily switch between data persistence implementations. JP3 shows switching between a "simple" (JDBC) and "remote" (EJB) implementations, but you could also be switching between a memory-based model (like the Struts Mailreader Example) and OJB, or anything else. Quite neat. In my own current work, I use iBATIS DAO to switch between memory-based persistence (a hashmap) and a JBDC database. It works quite well, all I do is change a comment in a properties file. I'm using Commons Chain http://cvs.apache.org/viewcvs/jakarta-commons-sandbox/chain/ to manage access to the business logic. The controller action calls a Command/Chain from the catalog using a logical name. I use the form name and the Command name, since the form name is also tied to validation, and validation is tied to what Command you are going to call. So the same token is used for all three constructs (Form, Validation, Command). Often, the Command is just a wrapper around a call to the DAO. Here's one for selecting a record by an id number: public boolean execute(Context context) throws Exception { CpuPermitDao dao = DaoConfig.getInstance().getCpuPermitDao(); CpuPermitContext ctx = dao.findByPermitNo(((CpuPermitContext) context).getPermitNo1()); if (null == ctx) return false; // couldn't find it context.putAll(ctx); return true; } The first line is the DAO discovery bit. DaoConfig is a singleton that uses a XML configuration to instantiate itself. Here's the bit that selects the DAO implementation: The XML configuration in turn can read in properties from a file, which is where the ${cpu-permit.impl} comes from them. This makes it quite easy to switch between implementations just by changing one line a properties file. The DAO class, "findByPermitNo" is responsible for fulfilling the "findByPermitNo": public CpuPermitContext findByPermitNo(String permitNo) throws DaoException { return find(Tokens.CN_PERMIT_NO, permitNo, true); } Here, it just calls a more generic member of the DAO class, filling in the attribute name for permitNo. The find method does the actually persistence work: public CpuPermitContext find(String column, String equals, boolean sensitive) throws DaoException { return (CpuPermitContext) executeQueryForObject(Tokens.CPU_PERMIT_SELECT_COLUMN, select(column, equals)); } The "executeQueryForObject" method is where the rubber meets the road. This is a call to the iBATIs SqlMaps framework. It looks up the SQL query for CPU_PERMIT_SELECT_COLUMN, and fills in the replacement parameters using the value of the column parameter. In the Mock implementation, I can reuse the findByPermitNo method and just replace the find method: public CpuPermitContext find(String property, String equals, boolean sensitive) { List list = search(property, equals, sensitive); if (0 == list.size()) return null; return (CpuPermitContext) list.get(0); } public search(String property, String equals, boolean sensitive) { MockDatabase db = MockDatabase.getInstance(); Iterator i = db.keySet().iterator(); String match = Utils.conform(equals, sensitive); List list = new ArrayList(); while (i.hasNext()) { CpuPermitContext ctx = (CpuPermitContext) db.get(i.next()); if (match.equals(Utils.conform(ctx.get(property), sensitive))) list.add(ctx); } return list; } The key is to create a sensible, high-level API for your application, like this: public interface CpuPermitDao extends Dao { public CpuPermitContext findByPermitNo(String permitNo) throws DaoException; public CpuPermitContext findBySystemId(String systemId) throws DaoException; void insert(CpuPermitContext context) throws DaoException; public List listApplicants() throws DaoException; public List listByApplicantName(String applicantName) throws DaoException; public List listPermitNo() throws DaoException; void update(CpuPermitContext context) throws DaoException; } that you can instantiate for different persistance strategies. If some methods can be reused between strategies, you can isolate those in a base class. Each strategy can then implement whatever abstract methods are left. Essentially, this API interface *is* your application. The Actions are just adapters that ferry data between HTTP and your business layer. We're calling them DAOs, since that's the most common use. But, they are really API objects that may also access the persistence layer. Don't think of it as just DAO, think of it as API with DAO. The current Struts MailReader example also demonstrates this same
Re: Keeping Actions clean - separating actions from business model from persistence
What I'm trying to grasp is where it it best for the business object <---> dao interaction to take place. OK, let's make an example, cause I'm having trouble thinking abstractly tonight... An online store customer selects several products, clicks "check out", which calls a CheckOutAction. From there: 1. The CheckOutAction retrieves a Customer and a List of Product's from the session; it creates an Order out of those components; it then calls placeOrder(Order) on a Store business class. 2. Store.placeOrder(Order) saves the Order to persistent storage; then uses an Emailer business class to emailOrderConfirmation(Order). --now the question: 1. Which component is responsible for discovering the DaoManager, retrieving the OrderDao from that manager, and telling the dao to save()? Is it: a. the Store.placeOrder() method? -or- b. the Order business object itself? Is it the business entity object's responsibility to discover and use its dao's, or that object's *user's* responsibility? Matt, you seem to forgo business entity classes and create DAO's right in your action, passing those to business "use case" classes... Mahesh, your business "use case" components seem to be the ones responsible for discovering the right DaoManager implementation, and retrieving the needed DAO classes... Anyone make the business entity classes themselves responsible for finding and using their respective dao's (say, when an Order is issued a save() command)? Thanks for all your input! -Sasha On 10/10/03 20:25, "Sgarlata Matt" <[EMAIL PROTECTED]> wrote: > I have a 4 tier architecture. > > PRESENTATION TIER > - Struts > - Action classes > > BUSINESS TIER > - Business Objects > > INTEGRATION & PERSISTENCE TIER > - DAO Manager > - DAOs > - Other database access mechanisms (I do some JDBC using a fancy home-grown > SQL building mechanism when dealing with particularly complex queries) > > RESOURCE TIER > - Databases - To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
Re: Keeping Actions clean - separating actions from business model from persistence
Matt, thanks for your quick feedback. > I use my own framework because I don't know any better. > > public abstract class DaoManager { > public abstract IRecordDao createDao(Connection conn, String daoClassName) > throws DaoException; Which tier calls your DaoManager? It seems from your code that the caller of DaoManager is responsible to knowing the database configuration information, as well as the implementing DAO class. Is it the Action? In other words, who orchestrates the interaction of business and dao classes? Does the action instantiate a business class and populate it from your ActionForm, then get a dao instance from a factory, and pass it the business class? Or is there another pattern to this? Thanks. > Matt -Sasha > - Original Message - > From: "Sasha Borodin" <[EMAIL PROTECTED]> > To: "Struts Users Mailing List" <[EMAIL PROTECTED]> > Sent: Friday, October 10, 2003 6:44 PM > Subject: Keeping Actions clean - separating actions from business modelfrom > persistence > > >> Ted, Matt, Joe, and all the other helpful folks that chimed in earlier on >> persistence mechanisms: >> >> In trying to keep with best practices, I've managed to remove all "model" >> related code (business logic, and persistence) out of the Actions' > execute() >> method. Now I'd like to take it one step further and decouple the > business >> model classes from the implementing persistence technology (btw, settled > on >> OJB for now :). From Joe's post, it seems like the DAO pattern is called >> for to accomplish this. >> >> My (slightly off topic) question is this: who develops their own DAO >> framework (like the dao and dao factory interfaces), and who uses a 3rd >> party framework (like iBATIS's Database Layer) and why? There was > something >> mentioned about the discovery of the persistence mechanism as well... >> >> Any references to webpages/books would be appreciated. >> >> BTW, I've been shamelessly posting to this list questions that are > probably >> better directed elsewhere. What would be a more appropriate list? >> >> Thank you, >> >> -Sasha >> >> >> - >> To unsubscribe, e-mail: [EMAIL PROTECTED] >> For additional commands, e-mail: [EMAIL PROTECTED] >> > > > - > To unsubscribe, e-mail: [EMAIL PROTECTED] > For additional commands, e-mail: [EMAIL PROTECTED] - To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
Keeping Actions clean - separating actions from business model from persistence
Ted, Matt, Joe, and all the other helpful folks that chimed in earlier on persistence mechanisms: In trying to keep with best practices, I've managed to remove all "model" related code (business logic, and persistence) out of the Actions' execute() method. Now I'd like to take it one step further and decouple the business model classes from the implementing persistence technology (btw, settled on OJB for now :). From Joe's post, it seems like the DAO pattern is called for to accomplish this. My (slightly off topic) question is this: who develops their own DAO framework (like the dao and dao factory interfaces), and who uses a 3rd party framework (like iBATIS's Database Layer) and why? There was something mentioned about the discovery of the persistence mechanism as well... Any references to webpages/books would be appreciated. BTW, I've been shamelessly posting to this list questions that are probably better directed elsewhere. What would be a more appropriate list? Thank you, -Sasha - To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]