One of the biggest challenges in developing web applications is the ability to alter the behaviour of your program once the application has been deployed.
If you are building a highly extensible application like a content management system or an e-commerce engine, you don’t necessarily want to restart or redeploy every time a change has been made. You also want the ability to define business rules, add custom scripts, and turn functionality on and off.
In this post, I will show you how to externalize application functionality using closures, the Groovy configSlurper and the pluggable behaviour pattern.
How it works.
The idea is pretty simple.
Within our main application, we define extension points that load programming logic from an outside source. The extensions can live in a database, the file system or within any other means of storage.
We treat them as any other extenalized resource. When we need to change the behaviour of our application or add new functionality, we simply add new extensions or modify our existing ones.
The Pluggable Behaviour Pattern.
One of the aha! moments for this exploration came when I was looking at the slides of Venkat Subramaniam’s presentations on Design Patterns in Java and Groovy. In it, he describes how to define the pluggable behaviour pattern in Groovy. Here are the relevant slides.
At the core of this pattern is the idea that you can encapsulate functionality you want to inject using Groovy closures.
All we have to do to make these closures into hot-deployable extensions is simply create a mechanism by which we can load them into our application.
How to implement this in Groovy and Grails.
Here is a proof of concept of this functionality. ( note: since I’m a lazy typer, the app runs at http://localhost:8080/ ).
In this sample, I’ve created a simple Payment Gateway service.
Extension files
Each of my payment providers is defined in an external file located within the extension/groovy folder and look like this:
name = "Not Authorize.net"
key = "authorizenet"
authenticate = { status ->
System.out.println( "Payment Provider 3 authenticate for ${status}" )
return 'authenticated'
}
processCreditCard = { cardNumber ->
System.out.println( "Payment Provider 3 --- process credit card ${ cardNumber }" )
return 'accepted'
}
---
As you can see, each extension is just a ConfigSlurper property file with String values and a few simple closures defined.
Loading extension files
The Payment Gateway Service class simply keeps an internal map of providers.
This is accessible to other parts of the system that need to use it via paymentGateway.providers.
To load or update the list of providers, the application simply reads the externally defined extension files and loads them into a ConfigSlurper object:
def refreshProviders = {
providers.clear()
new File( grailsApplication.config.externalFiles ).eachFile{ file ->
def config = new ConfigSlurper().parse( file.toURL() )
providers.put( config.key, config )
log.debug( "loaded provider ${config.name} from file ${ file }")
}
}
---
We use a ‘key’ property defined in each of the config files to identify the provider.
To make reloading easy, I added an action in my controller to reload my payment providers, this is found in CreditCardController ( grails-app/controller/CreditCardController ) and can be accessed via http://localhost:8080/refreshProviders.
Additionally, in grails-app/conf/Config.groovy, I specify the location of my extensions directory via:
externalFiles = "c:/external/groovy"
---
Finally, so we don’t start up with a list of empty providers, I added this to my bootstrap file ( grails-app/conf/bootstrap )
def paymentGatewayService
def init = { servletContext ->
paymentGatewayService.refreshProviders()
}
---
Creating a extensible method
In our PaymentGatewayService, I have a method called processCreditCard that takes in a provider key.
It uses this key to resolve the right payment provider to use:
def provider = providers[ providerKey ]
---
Once it knows which provider to use, calling the methods within the external extension file is simply a function call to the Config object.
def authenticationResult = provider.authenticate( status )
---
Here, the authenticate method is the one defined within the externalized extension file. As you can see, we can pass in parameters in and out of the script and chain different parameters. I can have many extension points within my methods. So if I was to write a shipPackage method for a provider like FedEx, DHL or Canada Post, for example, I could write it as
def calculateShippingRate = { key, item, addressObject ->
def provider = resolveProvider( key )
def address = provider.resolveAddress( addressObject )
def taxRate = provider.calculateTaxRate( item, address )
def packageDimension = provider.resolvePackageTier( item )
def shippingCost = provider.calculateShippingCost( packageDimension, taxRate, item )
return shippingCost
}
---
and then externalize each of these methods into closures.
Seeing it in action:
- Change your config.groovy file so that the extension directory points to the right location.
- Start the application under app with grails run-app
- Navigate to http://localhost:8080/creditCard/You will see three payment aggregators available under the provider dropdown. Add a dummy credit number and you will see each provider giving you a different behaviour.
- Go to your extensions/groovy directory and move two providers out of the folder.
- Navigate to http://localhost:8080/creditCard/refreshProviders , and then back to http://localhost:8080/creditCard/You will see that only one provider is available.
- Restore the providers and write your own, repeat step 6 for fresh, chewy providers.
Benefits of this approach.
I see three benefits to this approach – it simplifies your code, allows you to change it on runtime and lets you build manageable customizations.
1. Simplifying code
One of the nice things about this approach is that it forces you to write code in a generic way.
Let’s say I’m writing a generic payment gateway. Instead of worrying about Paypal or WorldPay or Authorize.net, I just need to write out bits of my application that assume this functionality works. It allows me to abstract away my implementation details and keeps my deployed code simpler.
2. Change behaviour at runtime
Another benefit of this is the ability to add new behaviours at runtime.
Let’s say that I have a new business requirement to support the WorldPay interface to provide payments. Traditionally, I would write a new class that supports WorldPay, add it to my application and deploy a new War file. By using extension points, I can simply write an extension and add it to my extensions folder. After I reload my list of extensions, the new behaviour is ready for use.
3. Manageable customizations
Grails plugins are great, but a lot of them have a ‘all or nothing’ approach to adding functionality.
With extensions, you can choose to deploy a small part of existing functionality. In our payment gateways example, we would have to add all supported gateways at deploy time to enable or disable them if we were not using extensions. If you look at the Magento Commerce site, you can see they have over 300 payment gateways. Loading them all at application startup would create unnecessary memory use. With extensions, you get to pick and choose which ones to use.
Unresolved questions about this stuff.
Loading / unloading of classes: I can imagine adding an evict / install closure and passing a classLoader into the actual extension file, but haven’t really tested this and don’t have a clear answer to how this would impact things like the PermGenSpace or cause exceptions.
Plugin interactions: It is still unclear to me how an extension could add a plugin it needs or modify the Spring or ApplicationContext on the fly without a restart. The Grails roadmap suggests that there would be a hot-deployable plugin infrastructure in place soon, which might solve this question.
Ideally, this technique would be used in the creation of new plugins. Instead of creating a separate plugin for Paypal or Google checkout, we would have one top level plugin that abstracts all the difficult parts of the implementation and use extensions for implementation details like web service definitions, etc.
Maybe it makes sense to make a plugin of this stuff with a nice little GUI for managing extensions and a domain object to properly store them.
相关推荐
解决Linux MySQL缺失依赖包 perl-Module-Pluggable-3.90-141.el6_7.1.x86_64.rpm
官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装
标题中的“PyPI 官网下载 | django-pluggable-contact-0.0.5.tar.gz”指的是一个在Python Package Index(PyPI)上发布的开源软件包。PyPI是Python开发者发布自己编写的Python模块和库的地方,使得其他开发者可以...
离线安装包,亲测可用
本节将深入探讨王治江在 FFA2019 大会上分享的两个关键技术:可插拔的 Shuffle 服务(Pluggable Shuffle Service)和不对齐检查点(Unaligned Checkpoint)。 01 可插拔的 Shuffle 服务 Shuffle 服务是 Flink 中...
在这个版本中,Oracle引入了一个革命性的新特性——可插拔数据库(pluggable database,简称PDB),这一特性极大地增强了数据库的灵活性和可维护性。 可插拔数据库(PDB)的核心理念是将一个物理数据库容器(CDB,...
离线安装包,亲测可用
ORA-65122_ Pluggable database GUID conflicts with the GUID of an existing_ITPUB博客.mhtml
标题中的“net20-pluggable-agent”是一个专为.NET Framework 2.0设计的可插拔代理,目的是为了在TestCentric GUI测试环境中运行测试。这个代理的主要功能是为了解决.NET 2.0应用程序与更高版本的测试框架之间的兼容...
借助React Pluggable,我们可以将我们的应用视为一组功能,而不是一组组件。 它提供了解决此问题的混合方法。 我们已经在大型复杂的应用程序(例如使用了React Pluggable,以随着时间的推移添加独立的和从属的功能...
在 `spring-pluggable-master` 压缩包文件中,很可能是包含了这个特性的示例代码或实现。你可以通过阅读源码来更深入地理解如何在实际项目中应用这种插件化的设计。这将有助于你掌握 Spring 如何处理动态插件加载和...
该存储库探讨了有关某种状态化Reagent组件的一些想法,在这里我将其称为“可插拔”组件。 我们提供了一种简洁,声明性的方式来编写具有设置和清除阶段的Reagent组件,而无需明确地编写React生命周期方法。...
"Punits",全称为“pluggable units”,是一个用C语言编写的、具有可扩展性的单元转换程序。它不仅提供了基本的控制台界面,而且开发者计划在未来添加图形用户界面(GUI),以提供更友好的用户体验。 "Punits"的...
**phook 概述** phook 是一个创新的开源项目,专为 IT 专业人士和开发者设计,它提供了在运行时向应用程序注入自定义代码的能力。这个工具的核心特性在于其模块化设计,允许用户通过插件扩展功能,以满足各种特定...
This document defines the low speed electrical and management interface specifications for enhanced Small Form Factor Pluggable(SFP+) modules and hosts. The SFP+ module is a hot pluggable small ...
这个模板旨在帮助创建能够轻松地添加、移除或替换功能模块的应用,使得项目的维护和扩展变得更加简单。下面将详细探讨这个模板的特点、优势以及如何利用它来构建可扩展的应用程序。 1. **可插拔组件系统**: ...
角度可插拔架构 这是我的文章的代码示例 ,一个允许动态插入功能的Angular应用程序。 该应用程序是一个带有控件的简单仪表板,这些控件在运行时从外部源加载。 此回购包含: dahboard-一个提供小部件仪表板的...
在低压电器领域,ABB同样提供了一系列高品质的产品,其中ABB Pluggable OVR-Multi-Pole产品就是其中的一个代表。 首先,Pluggable OVR-Multi-Pole是一种多极可插拔开关,是ABB低压配电产品线的一部分。多极开关在...
SFF-8432-R5.2a SFP+ Module and Cage Rev 5.2a. November 30, 2018. This specification defines the mechanical specifications for the SFP+ Module and Cage aka Improved Pluggable Formfactor (IPF).
1.5 Name and Logo Usage....20 1.5.1 Logo Use 20 1.5.2 Trademark polic 20 1.6 Intellectual Property 21 1.7 Special Word Usage 22 1.8 Connectors 1.8.1 Legacy CompactPCI Connectors 22 1.8.2 High -Speed ...