450 likes | 546 Vues
This research outlines how applying aspect-oriented software development to middleware frameworks can simplify the design, development, and modularity of enterprise applications. It introduces new programming language constructs applicable to various software development domains. The study addresses common issues faced in creating large-scale programs for sectors like banking and government agencies and proposes solutions such as Shakeins and Factories. Additionally, it discusses the integration of AOP with middleware frameworks and presents innovative contributions to enhance dynamic shakeins.
E N D
Applying Aspect-Oriented Software Development to Middleware Frameworks Tal Cohen Joint work with Yossi Gil
Research Overview • The problem: simplifying the design, development and modularity of enterprise applications. • Huge software programs for banks, government agencies, etc. • The research outcome: a set of programming language constructs. • Applicable to almost any software development domain. • The problem:simplifying the design, development and modularity of enterprise applications. • Huge software programs for banks, government agencies, etc. • The research outcome: a set of programming language constructs. • Applicable to almost any software development domain. begin::Overview Overview Problem Solution More Results
Contributions • Shakeins – aspect-like mechanism combining AOP with OOP. • AspectJ2EE – a proof-of-concept design for shakeins in Enterprise Java. • Factories – a language-level mechanism for managing object instantiation. • JTL –a language for Query-by-Example searches in a program source. • Object Evolution – objects can change type at runtime in type-safe systems. Overview Problem Solution More Results
Outline • The Problem: Enterprise Application Development. • "The unbelievable complexity of simple chores" • Overview of existing solutions: middleware frameworks, aspects… • … and why they aren't good enough • The Solution: Shakeins • How to integrate AOP with middleware frameworks • Additional Contributions • From better static pointcuts to dynamic shakeins end:: Overview Problem Solution More Results
Why Programming a Banking Application is Easy begin::Problem class Account { void transferTo(Account other, int amount) { if (balance < amount) throw new OverdraftException(); this.balance -= amount; other.balance += amount; } … Overview Problem Solution More Results
Why Programming a Banking Application is Not Easy class Account { void transferTo(Account other, int amount) { Log.log("Attempting transfer"); User user = Security.getContext().getCurrentUser(); if (!user.canTransferFrom(this)) Log.securityError(); throw new SecurityException(); Transaction tx = Db.getConnection().beginTransaction(); this.refreshFields(); other.refreshFields(); if (balance < amount) tx.rollback(); throw new OverdraftException(); this.balance -= amount; other.balance += amount; this.updateDb(); other.updateDb(); tx.commit(); Log.log("Transfer completed"); } … LOGGING SECURITY Overview Problem BUSINESS LOGIC Solution PERSISTENCE TRANSACTION MANAGEMENT More Results Non-functional concerns yield tangled and scattered code.
Making EnterpriseDevelopment Easier • Past: Work hard • Bonus: You get to use COBOL • Present: Middleware Frameworks • CORBA, DCOM, Enterprise Java • Future: Aspect-Oriented Middleware Frameworks • Past • Present • Future Overview Problem Solution More Results
Middleware Frameworksand Services • Enable large-scale, multi-tier application development. • Provide services attachable to business logic. • The (unreached) ideal: • You write the business logic, • The framework handles everything else. • Services are configurable. • e.g., by XML files. Overview Problem Solution More Results
Programming a Banking Application in J2EE • Set of services is pre-defined and not extensible. • So (e.g.) logging remains tangled and scattered. class AccountBean { void transferTo(Account other, int amount) { Log.log("Attempting transfer"); if (getBalance() < amount) throw new OverdraftException(); setBalance(getBalance() – amount); other.setBalance(other.getBalance() + amount); Log.log("Transfer completed"); } … LOGGING BUSINESS LOGIC Overview • Not trivial to use. • e.g., can't simply access the field balance. • Several support class required (not shown) – home, interface, etc. • Somewhat easier with v5. Problem <ejb-jar> <enterprise-beans> <entity> <ejb-name>Account</ejb-name> <home>come.acme.AccountHome</home> <remote>com.acme.Account</remote> <ejb-class>com.acme.AccountBean</ejb-class> <persistence-type>Container</persistence-type> … Persistence, transactions, security: services, configured using XML files. Solution • Services arenot programmable. • Need a different security model? No can do. • Can't control how persistence logs operations. More Results
AOP Aspect-Oriented ProgrammingTo The Rescue! • The new kid in town: a buzzword and a panacea solution to tangled and scattered code: • Decompose the system into distinct aspects (code modules). • Develop each aspect independently. • The aspects are then woven into a complete system. • Chief example: AspectJ Overview Problem Solution More Results
Decomposition and Weaving Clearly distinct concerns Executable Overview Problem Program requirements Aspect Weaving (mechanical process) Aspectual Decomposition (conceptual process) Solution More Results
Programming a Banking Application with AOP • Very extensible. • Define new aspects as you deem fitting. class Account { void transferTo(Account other, int amount) { if (balance < amount) throw new OverdraftException(); this.balance –= amount; other.balance += amount; } … BUSINESS LOGIC • Highly programmable. • Not just a pre-defined set of code transformations. Overview aspect Security { before execution of any public method in Account { User user = Security.getContext().getCurrentUser(); if (!user.canAccessAccount(this)) throw new SecurityException(); } } Everything else: defined as Aspects Problem aspect Logging { before execution of public methods in Account { Log.log("Began " + methodName); } after successful execution of same { Log.log(methodName + " ended successfully"); } after failed execution of same { Log.log(methodName + " caused exception: " + e); … Solution • Reusable and configurable. • e.g., the same logging aspect can be used for many classes. More Results
The Problem with Aspects • Aspects are very similar to inheritance in that they create variations on existing code. • However, there is a schism between aspects and inheritance. • A "paradigm mismatch" between AOP and OOP. • Worse, aspects do not scale. • Can't handle large enterprise applications. Overview Problem Solution More Results
Aspects as Class Modifiers • Unclear: How may an aspect modify the type? • Can it remove members? • Can it change method signatures? • Unclear: How may an aspect modify the instance creation mechanisms? • Can it require additional constructor parameters? • Insert code before super()? • Unclear: Does an aspect modify subclasses, too? • Does a subclass inherit from the modified version of the class, or from the original one? • Does an advice applied to "before execution of method m()" apply to execution of overridden versions of m, too? Overview Problem Solution More Results
Scalability? • Versatility • AOP: Aspect application is destructive. • Enterpriseapps: Legacy code must not be broken. • Plus, different modifications of same base class required by different parts of the program. • Configurability • AOP: Precedence problems with multiple aspects. • Enterpriseapps: Order matters – and should not be set globally. Overview Problem Solution More Results
Manageability? • Granularity • AOP: An aspect may apply to any class! • Enterpriseapps: Selective application required. • Seeing the Big Picture • AOP: Class is oblivious to aspects that modify it • Enterpriseapps: Need to know what aspects apply to a given class Overview Problem Solution More Results
Aspects Do Not Scale • The AspectJ philosophy: • Throw in all program modules (classes, aspects). • Let the compiler do the mixing. end:: Overview Problem Solution More Results
Shakeins • The basic principle: Use OOP to implement AOP. • Use the existing inheritance mechanism to implement class modifications. • Suddenly, all the problems disappear. • No paradigm mismatch… begin::Solution Overview Problem Solution More Results
What is a Shakein? • A Shakein makes a re-implementation of a class. • It does not change the type. • We get a new implementation of an existing type. • The old implementation continues to exist! • A parameterized, generic-like structure. Overview Problem Solution More Results
Shakeins as “Reimplements” Operators A class in OOP is: • A type declaration C p; • Template for object creation (constructor, field layout etc.) p = new C(); • An implementation p.m(); Simple subclassing: • Creates a subtype by adding more functions to the protocol. • Extends the structure definition by adding more fields. • Modifies the behavior by replacement and refinement Given a class C, shakein S, the application C’=S[p1,…,pn]<C> is a new class such that • C’ has the same type as C. • Cannot declare variables of type C’ • May have additional fields. • May have a modified behavior. Overview Problem Solution More Results
Aren't These Just Mixins? • A mixin takes a class parameter and generates a subclass. • M<C> extends C. • Encapsulates the "delta" between layers of inheritance. • However, unlike shakeins, mixins: • Create a new type. • Must be highly aware of the superclass. • Can't include instructions to "add security tests to every public method". • Unparameterized (except for the superclass). Overview Problem Solution More Results
Shakein Example Note: not "public methods in Account" Implicit type parameter ("<T>") Overview shakein Logging { before execution of public methods { Log.log("Began " + methodName); } after successful execution of same { Log.log(methodName + " ended successfully"); } after failed execution of same { Log.log(methodName + " caused exception: " + e); … Problem Solution More Results
Using a Shakein • Account accnt = new Logging<Account>(); • The shakein application is explicit. • The variable type is the (original) type. • Shakeins do not define new types, only new implementations! Overview Problem Solution More Results
Applying a Shakein to a Class Hierarchy Type C1 C1 C1’ Overview Type C2 Type C3 C2 C2’ C3 C3’ Problem Solution Type C4 No inheritance relationship between the re-implementations C4 C4’ More Results Since no new types are defined, this does not disturb polymorphism!
Shakeins Do Scale • Explicit application – no undesired classes are matched, ever. • Original class still exists – existing code is not broken. • If the changes are desired in existing code, only instance creation points have to be changed. • See our work on factories… • Explicit and flexible ordering – you can have Logging<Secure<Account>>, or Secure<Logging<Account>>, or both. Overview Problem Solution More Results
Parameterization • What if we want different instances of Account to log into different log files? • With aspects – though luck. • With shakeins – just add a parameter to the shakein: Overview Problem shakein Logging[String filename] { before execution of public methods { Log.log(filename, "Began " + methodName); } after successful execution of same { Log.log(filename, methodName + " ended"); } … Solution More Results Account a = new Logging["a.log"]<Account>(); Account b = new Logging["b.log"]<Account>();
Repeated Applications • We can even apply the same shakein more than once to the same class. • Possibly with different parameters. • Example: Log["pre"]<Secure<Log["post"]<Account>>>() Overview Log of operations after security tests. Problem Solution Log of operations before security tests. More Results
Pointcut Parameters • A "pointcut" is the expression that specifies where the aspect/shakein code ("advice") should be injected. • Examples: "execution of public methods", "read access of field f", etc. • Shakeins support pointcut parameters. Overview Problem Solution shakein Secure[pointcut p, String role] { before p { SecurityContext().requireRole(role); } } More Results
Pointcut Parameters + Repeated Application Secure[Pt, "teller"]< Secure[Pc, "client"]<Account>> • Pt = execution of createNew || changeOwner • (teller-only operations) • Pc = execution of transferTo || withdraw • (client operations) Overview Problem Solution More Results
Shakein Composition shakein StdPersistence = Transactional ○ Persistent; shakein MySecure = Secure[Pt, "teller"] ○ Secure[Pc, "client"]; Overview Problem Solution More Results
Other Middleware AOP Solutions • JBoss AOP: Extension to the JBoss open-source J2EE server. • Works by bytecode manipulation. • Supports runtime application/removal of aspects. • Susceptible to runtime failure if not configured properly. • Spring AOP: Open-source "lightweight" middleware framework. • Works using interceptors. • Pointcuts evaluated at runtime. Overview Problem Solution More Results
Feature Comparison Overview Problem Solution More Results
Performance Comparison • Given a class Point, apply aspects (shakeins) to confine the valid range of values for x and y. • Classic example of repeated, parameterized application. • Code in all 3 versions optimized for repeated application (no code duplication) rather than performance. Overview Problem Solution More Results
How Do You Take Your Aspects? Shaken, not stirred. end:: Overview Problem Solution More Results
AspectJ2EE • An integration of the shakein concept into existing J2EE frameworks and applications. • Each service defined as a shakein. • Shakein application managed by XML configuration files. • No expressions like Secure[Pt, "teller"]<Secure[Pc, "client"]<Account>> appear in the code. • J2EE Home objects ensure that all instances are created with shakeins applied. • Deploy-time weaving. Overview begin::More Results Problem Solution More Results
JTL: The Java Tools Language • Joint research with Itay Maman and Evelina Zarivach. • The AspectJ pointcut-specification sublanguage is generally viewed as insufficient. • JTL is a Datalog-like language for code queries that can (among other things) replace AspectJ's pointcut sublanguage. • Based on Query-by-Example. • No need for looping constructs. • Rich predefined predicate library. Overview Problem Solution More Results
JTL Examples • Search for float-typed static method: • Regular exp.: static.*float.*\(.*\) • Inaccurate. • AspectJ: static float *.*(..) • OK, for this specific use. • XQuery: /class/method[ .//returns/@type="float" and @modifiers="static" ] • Insane. • JTL: static float method Overview Problem Solution More Results
JTL Examples • Another query: "any public field in a class that contains no setter or getter methods". • Regular exp.: impossible. • AspectJ: impossible. • XQuery: Possible, but… • JTL: Overview Problem setter := publicvoid 'set[A-Z]?*'(_); getter := public !void 'get[A-Z]?*'(); field_in_plain_class := public field, declared_in[C], C.members: { no getter; no setter; }; -- Given the standard library: field_in_plain_class := public field, declared_in[C], C { no getter; no setter; }; Solution More Results
Factories • Remove the need for the Abstract Factory and Factory Method design patterns. • Used extensively in middleware frameworks (e.g., home objects in J2EE, Bean Factory in Spring, etc.) • Ensure all instances are created with shakeins applied – without touching client code. • Constructors only deal with initialization; factories deal with object creation. • Including: which actual class to instantiate, or if we should use an existing instance. Overview Problem Solution More Results
Factories • With existing patterns: if class S is changed to become a singleton, all existing code that calls new S() must be changed to call S.getInstance(). • With factories, the change is local to S. • The factory S() will manage a single instance. • With existing patterns: if B extends A, and A has controlled instantiation via a getInstance method, what will B.getInstance() return? • With factories: such static methods are not needed. Overview Problem Solution More Results
Object Evolution • Dynamic reclassification: allowing an object to change its type at runtime. • e.g., prince → frog. • Supported by Smalltalk, several others. • Many real-world uses • e.g., State design pattern. • Type safety problems… Prince p = new Prince(); if (…) p → Frog(); p.drawSword(); // potential runtime type error • Our solution: limit to monotonic changes only. • "Object evolution" -- moving down the inheritance tree. • Prince → king is okay! Overview Problem Solution More Results
Object Evolution Flavors • I-Evolution: Movement down the inheritance tree. • Might fail if runtime type differs from static type. • M-Evolution: Move by applying a mixin to the runtime type of the object. • No "type mismatch" possible. • Failure even less likely with idempotent mixins. • S-Evolution: Move by applying a shakein to the runtime type of the object. • Type not changed at all (only the class), so… • Changes can be undone – still monotonic! • Result: shakeins as "dynamic aspects". • I-Evolution: Movement down the inheritance tree. • Might fail if runtime type differs from static type. • M-Evolution: Move by applying a mixin to the runtime type of the object. • No "type mismatch" possible. • Failure even less likely with idempotent mixins. • S-Evolution: Move by applying a shakein to the runtime type of the object. • Type not changed at all (only the class), so… • Changes can be undone – still monotonic! • Result: shakeins as "dynamic aspects". Overview Problem Solution More Results