Lately I have been thinking that the standard service-calling-the-dao-layer architecture hasn’t been working out as well as I would hope. The applications I have been working on have been using Spring and Hibernate with a dao object per model object. While this does provide a good separation between the two, I have been finding it increasingly difficult to write good tests for the service layer as the project matures. Past experience has shown that if writing tests is difficult, then it just isn’t done. Follow along as I think about possible ways to address this issue.
I’m sure you’ve all been there before. After months of creating page after page of crud screens, you’re now asked to create a read-only view of everything. I usually see this implemented with setting the readOnly attribute on every field to a variable that indicates whether or not the screen is editable. While this works, I don’t like it for three reasons:
- I would not be confident that the readOnly value will be correctly added to every field that is later added to the application.
- I would not trust the person originally implementing this change to modify every input the first time around. Chances are a couple will be missed.
- I would hate to have this task assigned to me and I would hate even more giving it to someone else. “Here, go add this flag to every input on these forty screens…”
Below is my latest attempt at a general solution to this problem. Caveat Emptor – I’ve never deployed this code to a production app and I fully expect to be bit by some huge limitation when/if I ever do. Consider it food for thought…
If you are ever working with a database where data is consistently being imported, dropped, and/or restored; sooner or later you will find yourself in the situation where the primary key of a table is no longer synchronized with the sequence used to generate it.
You can’t ignore the fact that web servers are multithreaded. We can hide as much as we want, but sooner or later you’ll find yourself in the situation where your application works fine during development and testing; but once it hits production you start hearing about “funny” things happening. While there are plenty of tools that can be used to simulate multiple users, they aren’t always the easiest to run locally and they seem to take to much time to modify while hot on the trail of a multithreading bug. Here I’ll discuss the approach I took when I was recently faced with this situation.
A while back, I read blog post discussing who is the client. Software projects frequently have many different clients, many of whom are frequently underrepresented throughout the development process. Do you know who all of the clients of your application are? What can you do to make their lives easier or better? You might be excited that you’re project is converting an old green-screen application to a web application, but have you thought about the data entry staff? How long did it take for them to enter a widget through the old interface? How about with the new one? How about the people who consume the data that your application will be collecting? Will they be able to access everything they need? Don’t forget the IT staff. Will they be able to support your application?
When developing software, you need to be aware of all of the different customers you have. Even though they might not be represented in the meetings, whether or not your software satisfies their needs will determine your project’s success.
There was a post a while back on TechRepublic about how leaders ask questions. When was the last time you asked yourself what could go wrong? What are you doing to prevent it, or minimize it’s damage?
I just got done reading Release It by Michael Nygard. I don’t remember the exact numbers he used, but he made the point that any system of sufficient size should expect to experience more than one “once in a million” situations. Assume that your system performs 1000 transactions a day, 365 days a year; after three years you will have processed over a million transactions. I don’t know about you, but saying something will happen about once every three years sounds a lot different than once in a million. So next time you’re in the car, waiting for traffic, start thinking about “what if.”
I recently had to implement a common feature across multiple applications and app servers, all of which point to the same Oracle database. For reasons unrelated, I chose to implement this feature using PL/SQL. You can all stop laughing now. I ended up with something resembling:
CREATE OR REPLACE PACKAGE BODY MY_PKG IS
g_enabled BOOLEAN := TRUE;
PROCEDURE enable_sp() IS
g_enabled := TRUE;
PROCEDURE disable_sp() IS
g_enabled := FALSE;
FUNCTION is_enabled_fn() IS
IF g_enabled THEN
l_return_value := 1;
l_return_value := 0;
Seems pretty innocent, doesn’t it?
So, I write unit tests in java to test everything. It all seems to be working. It goes through code review. Nothing more serious than cosmetic issues were found. It goes through the qa cycles. It all seems to be working. It gets deployed to production. At first, it seems to be working. Then I start to see some interesting behavior.
PL/SQL data types aren’t as straight forward as I would have liked. The variable g_enabled is of type BOOLEAN. This is a data type built into PL/SQL. It is not a data type in Oracle’s version of SQL. Why is this significant? Consider the two following scenario’s and their result (assuming that is_enabled=TRUE) –
This will print out a 1 to the standard output.
SELECT my_pkg.is_enabled_fn() FROM dual;
This query will return 0.
This was not what I expected. I would have expected the second query to return a 1. The reason it doesn’t, is that the query is not run in a PL/SQL block. This results in the BOOLEAN data type to not be defined and the if statement in is_enabled_fn() to always evaluate to false.
Package variables are connection private. I create two connections, A and B, to the database. If I call disable_sp() in connection A, is_enabled_fn() will still return a 1 in connection B. The value of the variable is not shared across the connections. If I were then to create a connection C, is_enabled_fn() will return 0. This is because connection C was created after the value was set in connection A. While it wasn’t what I initially expected, it makes some sense in that the connections don’t share memory. The really big problem comes into play when app servers pool connections and might not close them for weeks at a time.
I saw a really great post on the Freakonomics blog talking about how hidden connections almost sunk Chicago. I’ve seen situations like this all too often during my career. At least in the software world, we can try to prevent this by developing modules that are more loosely coupled. Of course, in the real world you are often inheriting years upon years of past decisions made by anonymous developers. In the absence of a thorough end-to-end testing suite, is there anything you can do to prevent this?
In Confessions of an IT pro, Becky Roberts talks about her nine biggest professional blunders. It really brought back some old memories. Maybe I’ll write a later post discussing some of my less-than-finest moments. A minor points in the article really struck me and I wanted to point them out:
Mistake #1 – Okay, you’ve royally screwed up. Do you try to get out of it? Or, do you admit your mistake, try to fix it (if you can), and deal with the consequences. I’d also like to give bonus points to the boss in this case for handling the news so graciously.
Mistake #2 – I assume this story is a bit dated since the program was in basic, but I always am amazed when there are no test systems. It happened back then, it still happens today. I’ll be the first to admit that I make mistakes. Lots of them. It was also refreshing to hear that she struggled with how hard she should dig in her heels and fight when she found herself in a bad situation. Do you tell the boss its a bad idea and do it anyways when he tells you to? Do you refuse? I’m not sure I have a good answer for that one.
Mistake #6 – I’ll admit that I’m bad at this one. I have the utmost respect for people who are good at this. If anyone has any pointers on how they developed this habit, I’m all ears.
Mistake #7 – I think this situation is all-to-common. Especially in organizations that are matrix-managed and have individuals split across multiple projects. Her last sentence struck me though. I’ve never had it work for me. I don’t know if that is a product of the particular environment I was in at the time, or if I wasn’t effective in presenting my case.
Not a life-changing post, but a refreshing reminder of the bumps and bruises everyone seems to experience along the way.