To avoid having to merge your code changes when you receive product patches or updates, we recommend that you avoid making direct changes to the Elastic Path Commerce source code. This section presents a number of strategies to help you eliminate or reduce the number of direct changes you need to make.
Binary-Based Development allows you to customize the Elastic Path Commerce components without making changes to the core source code. This can greatly reduce the time and effort required when upgrading to newer versions of the product. The goal of binary-based development is to keep your code customizations separate from the vendor (i.e., Elastic Path's) source code. Key benefits include:
- Reduced time and cost of upgrades. You do not need to merge changes at the source code level. Simply drop in the modified binaries.
- Accelerated development. You do not need to build the entire code base. You only need to build your customizations.
- Improved ability to leverage Maven features such as dependency management and plugins.
Code organization and package structure
Store your extension code in a different packages than the core application source code and build it in a separate library. You will need to ensure that your library of custom code is included in the Maven repository used by the core applications.
Use an extension package structure that mirrors the Elastic Path package structure. For example, to customize the core classcom.elasticpath.domain.customer.impl.CustomerImpl, your extension class would be com.yourcompany.domain.customer.impl.YourCustomerImpl.
Customizing domain objects
Before customizing a domain object, check to see if the current implementation supports localized attributes; many domain objects can be extended without writing any code by adding attributes via the Commerce Manager. Using attributes is generally preferred, but there is some additional performance overhead associated with retrieving attribute values in high-traffic areas of the application. You will need to consider how the new properties will be used and whether it is worth the time and effort to customize the source code.
Extending domain classes
You can add properties to a domain object class by extending them with your own Java classes. Any accessor or convenience methods that perform some logic that you need to change can be overridden in your extending class. If you need to make many changes to a domain object, but you want to maintain the same interface (recommended), then you can create a new implementation of the domain object's interface.
Wrapping domain objects (the decorator pattern)
To customize a domain object only in certain scenarios while leaving it unchanged in the rest of the application, consider using the decorator pattern. Create a new implementation of the interface that the domain object uses and add a property to it that references the original domain object instance. Implement all the property accessors and other methods required by the interface and have them delegate the call to the reference of the domain object that you have wrapped, adding custom logic where needed. In the service/controller method where you need to use this customized wrapper, create an instance of the wrapper object and insert the original domain object into it using the setter method you created. Since the interface is identical between the wrapper and the original object it will be indistinguishable to most of the application.
If you are working with large collections of domain objects that need to be wrapped, you should consider extending the domain object instead; iterating over the collection and wrapping each object may cause some performance loss. If you only need to wrap a single object or a small collection of objects, using the decorator pattern to wrap them should be fine.
If the logic inside a service method does not need to be altered, but additional routines need to be run before or after the method call, consider using the Spring AOP framework's method interception capabilities.
With a few changes to the Spring configuration files, you can add hooks to an existing service method and perform additional logic before or after the method's execution. If you need access to the parameters passed into the service method, you can use a method interceptor. After configuring a proxy class in the Spring configuration file, you can re-route the method call to your own method, perform the custom logic, and then redirect the call to the original method.
The following example shows a method interceptor for validating objects passed in as method parameters.
<!-- Note: the referenced class needs to implement org.aopalliance.intercept.MethodInterceptor --> <bean id="validationInterceptor" class="com.elasticpath.service.interceptor.ValidateInterceptor"> <property name="defaultBeanValidator"> <ref bean="defaultBeanValidator" /> </property> </bean>
<bean id="validationAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice"> <ref bean="validationInterceptor" /> </property> <property name="patterns"> <list> <value>.*add.*</value> <value>.*update.*</value> </list> </property> </bean>
<bean id="customerAjaxController" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>com.elasticpath.sfweb.ajaxservice.CustomerAjaxController</value> </property> <property name="interceptorNames"> <list> <value>validationAdvisor</value> <value>customerAjaxControllerTarget</value> </list> </property> </bean>
Extending service classes
In order to change the internal logic of a service method, you can extend the service class with your own. If you need to change the logic of a significant number of methods in a service class, you can create a new implementation of the service class's interface. After doing this, edit the Spring service configuration files to point to your newly created service instead of the original one.
When you absolutely need to modify Elastic Path source code...
There may be some occasions where the previous practices cannot be applied. In these situations, try to follow these guidelines:
- Implement custom logic blocks as methods in an external class that can be invoked from the core class. This makes it easier for the person merging changes to see where custom code needs to be added after an upgrade.
- Avoid interspersing many small changes in an area of code. Instead, identify a discrete logical unit (e.g., a method, a control block), comment it out, and re-implement it in an external class that can be invoked in a single line of code.