jim stafford
The student will learn:
constructs and styles for implementing code reuse
what is a framework
what has enabled frameworks
a historical look at Java frameworks
At the conclusion of this lecture, the student will be able to:
identify the key difference between a library and framework
identify the purpose for a framework in solving an application solution
identify the key concepts that enable a framework
identify specific constructs that have enabled the advance of frameworks
identify key Java frameworks that have evolved over the years
Code reuse is the use of existing software to create new software. [1]
We leverage code reuse to help solve either repetitive or complex tasks while
not repeating ourselves
reducing errors
achieving more complex goals
On the positive side:
delegate a portion of our job to code that has been proven to work
should not need to again test what we are using
On the negative side:
reuse can add dependencies bringing additional size, complexity, and risk to our solution.
If all you need is a spoon — do you need to bring the entire kitchen?
Code reuse can be performed using several structural techniques
We can wrap functional logic within a method within our own code base. We can make calls to this method from the places that require that task performed.
We can capture state and functional abstractions in a set of classes. This adds some modularity to related reusable method calls.
Abstract interfaces can be defined as placeholders for things needed but supplied elsewhere. This could be because of different options provided or details being supplied elsewhere.
Reusable constructs can be packaged into separate physical modules so that they can be flexibly used or not used by our application.
There are two basic styles of code reuse and they primarily have to to with control.
Figure 1. Library/ Framework/Code Relationship [2] |
|
Its not always a one-or-the-other style. Libraries can have mini frameworks within them. Even the JSON/XML parser example can be a mini-framework of customizations and extensions.
A successful software framework is a body code that has been
developed from the skeletons of successful and unsuccessful solutions of the past and present within a common domain of challenge
A framework is a generalization of solutions that provides for
key abstractions,
opportunity for specialization, and
supplies default behavior to make the on-ramp easier and also appropriate for simpler solutions
"We have done this before. This is what we need and this is how we do it."
A framework is much bigger than a pattern instantiation.
A pattern is commonly at the level of specific object interactions.
We typically have created or commanded something at the completion of a pattern — but we have a long way to go in order to complete our overall solution goal.
Pattern Completion: "that is not enough — we are not done"
Framework Completion: "I would pay (or get paid) for that!"
A successful framework is more than many patterns grouped together
Many patterns together is just a sea of calls
like a large city street at rush hour
pattern of when people stop and go, make turns, speed up, or yield to let someone into traffic
individual tasks are accomplished, but even if you could step back a bit — there is little to be understood by all the interactions
"Where is everyone going?"
A framework normally has a complex purpose
We have typically accomplished something of significance or difficulty once we have harnessed a framework to perform a specific goal
Users of frameworks are commonly not alone
Similar accomplishments are achieved by others with similar challenges but varying requirements
"This has gotten many to their target. You just need to supply …"
Well designed and popular frameworks can operate at different scale — not just a one-size-fits-all all-of-the-time
different sized environments or
simply for developers to have a workbench to learn with, demonstrate, or develop components for specific areas
"Why does the map have to be actual size?"
The following distinguishing features for a framework are listed on Wikipedia. [3] I will use them to structure some further explanations.
Unlike a procedural algorithm where our concrete code makes library calls to external components, a framework calls our code to do detailed things at certain points. All the complex but reusable logic has been abstracted into the framework.
"Don’t call us. We’ll call you." is a very common phrase to describe inversion of control
Users of the framework do not have to supply everything. One or more selectable defaults try to do the common, right thing.
Remember — the framework developers have solved this before and have harvested the key abstractions and processing from the skeletal remains of previous solutions
To solve the concrete case, users of the framework must be able to provide specializations that are specific to their problem domain.
Framework developers — understanding the problem domain — have pre-identified which abstractions will need to be specialized by users. If they get that wrong, it is a sign of a bad framework.
A framework has a tangible structure; well-known abstractions that perform well-defined responsibilities. That tangible aspect is visible in each of the concrete solutions and is what makes the product of a framework immediately understandable to other users of the framework.
"This is very familiar."
A process to enable Inversion of Control (IoC), whereby objects define their dependencies [4] and the manager (the "Container") assembles and connects the objects according to definitions.
The "manager" can be your setup code ("POJO" setup) or in realistic cases a "container" (see later definition)
A Plain Old Java Object (POJO) is what the name says it is. It is nothing more than an instantiated Java class.
A POJO normally will address the main purpose of the object and can be missing details or dependencies that give it complete functionality.
Those details or dependencies are normally for specialization and extensibility that is considered outside of the main purpose of the object.
Example: POJO may assume inputs are valid but does not know validation rules.
A component is a fully assembled set of code (one or more POJOs) that can perform its duties for its clients. A component will normally have a well-defined interface and a well-defined set of functions it can perform.
A component can have zero or more dependencies on other components, but there should be no further mandatory assembly once your client code gains access to it.
A generalized term that tends to refer to an object in the range of a POJO to a component that encapsulates something. A supplied "bean" takes care of aspects that we do not need to have knowledge of.
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container. [4]
You will find that I commonly use the term "component" in the lecture notes — to be a bean that is fully assembled and managed by the container. |
A container is the assembler and manager of components.
Both Docker and Spring are two popular containers that work at two different levels but share the same core responsibility.
Docker supplies a container that assembles and packages software so that it can be generically executed on remote platforms.
A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. [5]
Spring supplies a container that assembles and packages software to run within a JVM.
(The container) is responsible for instantiating, configuring, and assembling the beans. The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata. The configuration metadata is represented in XML, Java annotations, or Java code. It lets you express the objects that compose your application and the rich interdependencies between those objects. [6]
Containers do more than just configure and assemble simple POJOs. Containers can apply layers of functionality onto beans when wrapping them into components. Examples:
Perform validation
Enforce security constraints
Manage transaction for backend resource
Perform Method in a separate thread
The following two examples are examples of straight POJO calls. There is no interpose going on here.
In the first example, method | Figure 2. POJO Buddy Call |
Figure 3. Self-Instantiated POJO Call | In the second example, method |
In this third example, method m1()
and method m2()
are in two separate classes (Class1
and Class2
) — but those classes have been defined as beans to the container.
That means both Class1
and Class2
will be instantiated as beans by the container.
Bean2
will be augmented with zero or more layers of functionality — called interpose — to implement the full bean component definition.
Bean1
will have the Bean2
injected to satisfy its Class2
dependency and be augmented with whatever functionality its is required to complete its bean component definition
This is how features can be added to simple looking POJOs when we make them into beans.
As stated earlier, frameworks provide a template of behavior — allowing for configuration and specialization. Over the years, the ability to configure and to specialize has gone through significant changes with language support.
Prior to Java 5, the primary way to identify components was with an XML file. The XML file would identify a bean class provided by the framework user. The bean class would either implement an interface or comply with JavaBean getter/setter conventions.
Early JavaEE EJB defined a set of interfaces that represented things like stateless and stateful sessions and persistent entity classes. End-users would implement the interface to supply specializations for the framework. These interfaces had many callbacks that were commonly not needed but had to be tediously implemented with noop return statements — which produced some code bloat.
Early Spring bean definitions used some interface implementation, but more heavily leveraged compliance to JavaBean setter/getter behavior and Java reflection. Bean classes listed in the XML were scanned for methods that started with "set" or "get" (or anything else specially configured) and would form a call to them using Java reflection. This eliminated much of the need for strict interfaces and noop boilerplate return code.
By the time Java 5 and annotations arrived in 2005 (late 2004), the Java framework worlds were drowning in XML. During that early time, everything was required to be defined. There were no defaults.
Although changes did not seem immediate, the JavaEE frameworks like EJB 3.0/JPA 1.0 provided a substantial example for the framework communities in 2006. They introduced "sane" defaults and a primary (XML) and secondary (annotation) override system to give full choice and override of how to configure. Many things just worked right out of the box and only required a minor set of annotations to customize.
Spring went a step further and created a Java Configuration capability to be a 100% replacement for the old XML configurations. XML files were replaced by Java classes. XML bean definitions were replaced by annotated factory methods. Bean construction and injection was replaced by instantiation and setter calls within the factory methods.
Both JavaEE and Spring supported class level annotations for components that were very simple to instantiate and followed standard injection rules.
Java 8 brought in lambdas and functional processing, which from a strictly syntactical viewpoint is primarily a shorthand for writing an implementation to an interface (or abstract class) with only one abstract method.
You will find many instances in modern libraries where a call will accept a lambda function to implement core business functionality within the scope of the called method. Although — as stated — this is primarily syntactical sugar, it has made method definitions so simple that many more calls take optional lambdas to provide convenient extensions.
In this section I am going to list a limited set of key Java framework highlights. In following the primarily Java path for enterprise frameworks, you will see a remarkable change over the years.
The Common Gateway Interface (CGI) was the cornerstone web framework when Java started coming onto the scene. [7] CGI was created in 1993 and, for the most part, was a framework for accepting HTTP calls, serving up static content and calling scripts to return dynamic content results. [8]
The important parts to remember is that CGI was 100% stateless relative to backend resources. Each dynamic script called was a new, heavyweight operating system process and new connection to the database. Java programs were shoehorned into this framework as scripts.
Jakarta EE, formerly the Java Platform, Enterprise Edition (JavaEE) and Java 2 Platform, Enterprise Edition (J2EE) is a framework that extends the Java Platform, Standard Edition (Java SE) to be an end-to-end Web to database functionality and more. [9] Focusing only on the web and database portions here, JakartaEE provided a means to invoke dynamic scripts — written in Java — within a process thread and cached database connections.
The initial versions of Jakarta EE aimed big. Everything was a large problem and nothing could be done simply. It was viewed as being overly complex for most users. Spring was formed initially as a means to make J2EE simpler and ended up soon being an independent framework of its own.
J2EE first was released in 1999 and guided by Sun Microsystems. The Servlet portion was likely the most successful portion of the early release. The Enterprise Java Beans (EJB) portion was not realistically usable until JavaEE 5 / post 2006. By then, frameworks like Spring had taken hold of the target community.
In 2010, Sun Microsystems and control of both JavaSE and JavaEE was purchased by Oracle and seemed to progress but on a slow path. By JavaEE 8 in 2017, the framework had become very Spring-like with its POJO-based design. In 2017, Oracle transferred ownership of JavaEE to Jakarta. The framework seems to have paused for a while for naming changes and compatibility releases. [9]
Spring 1.0 was released in 2004 and was an offshoot of a book written by Rod Johnson "Expert One-on-One J2EE Design and Development" that was originally meant to explain how to be successful with J2EE. [10]
In a nutshell, Rod Johnson and the other designers of Spring thought that rather than starting with a large architecture like J2EE, one should start with a simple bean and scale up from there without boundaries. Small Spring applications were quickly achieved and gave birth to other frameworks like the Hibernate persistence framework (first released in 2003) which significantly influenced the EJB3/JPA standard. [11]
The Jakarta Persistence API (JPA), formerly the Java Persistence API, was developed as a part of the JavaEE community and provided a framework definition for persisting objects in a relational database. JPA fully replaced the original EJB Entity Beans standards of earlier releases. It has an API, provider, and user extensions. [12] The main drivers of JPA where EclipseLink (formerly TopLink from Oracle) and Hibernate.
Frameworks should be based on the skeletons of successful implementations Early EJB Entity Bean standards (< 3) were not thought to have been based on successful implementations.
The persistence framework failed to deliver, was modified with each major release, and eventually replaced by something that formed from industry successes. |
JPA has been a wildly productive API. It provides simple API access and many extension points for DB/SQL-aware developers to supply more efficient implementations. JPA’s primary downside is likely that it allows Java developers to develop persistent objects without thinking of database concerns first. One could hardly blame that on the framework.
Spring Data is a data access framework centered around a core data object and its primary key — which is very synergistic with Domain-Driven Design (DDD) Aggregate and Repository concepts. [13]
Persistence models like JPA allow relationships to be defined to infinity and beyond.
In DDD the persisted object has a firm boundary and only IDs are allowed to be expressed when crossing those boundaries.
These DDD boundary concepts are very consistent with the development of microservices — where large transactional, monoliths are broken down into eventually consistent smaller services.
By limiting the scope of the data object relationships, Spring has been able to automatically define an extensive CRUD (Create, Read, Update, and Delete), query, and extension framework for persisted objects on multiple storage mechanisms.
We will be working with Spring Data JPA and Spring Data Mongo in this class. With the bounding DDD concepts, the two frameworks have an amazing amount of API synergy between them.
Spring Boot was first released in 2014. Rather than take the "build anything you want, any way you want" approach in Spring, Spring Boot provides a framework for providing an opinionated view of how to build applications. [14]
By adding a dependency, a default implementation is added with "sane" defaults.
By setting a few properties, defaults are customized to your desired settings.
By defining a few beans, you can override the default implementations with local choices.
There is no external container in Spring Boot. Everything gets boiled down to an executable JAR and launched my a simple Java main (and a lot of other intelligent code).
Our focus will be on Spring Boot, Spring, and lower-level Spring and external frameworks.
In this module we:
identified the key differences between a library and framework
identify the purpose for a framework in solving an application solution
identify the key concepts that enable a framework
identify specific constructs that have enabled the advance of frameworks
identify key Java frameworks that have evolved over the years