markb's profileWindows Live spacePhotosBlogListsMore Tools Help

Windows Live space

Hierarchial State Machine For CRUD

 

image

http://www.amazon.com/Practical-Statecharts-C%2B%2B-Programming-Embedded/dp/1578201101/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1201814704&sr=8-1

First of all the HSM pattern is very similiar to the Chain Of Responsibility pattern from the GO4 book.  However it's more like the Tree Of Responsibility since there are multiple chains.  Now what an HSM models, the problem it solves is the following. 

The HSM pattern models the context in terms of state in your software that you would have to reference in the branch logic of the event handling code in order to respond intelligently to requests and events. 

We frequently have to construct the context the software is in at a particular point in time in the event handling of our software.  When it comes to managing CRUD ( Create, Read, Update Delete ) for an entity from data services, we need to know if the request times out, if the request immediately failed , if the entity is empty, if an entity was fetched successfully , if the entity is in the process of being saved, if the entity timed out being saved, if an error came back while the entity was being deleted; etc… , etc… etc…  This is nothing new or profound  - it is a solved problem in code.  We write essentially ad-hoc code that examines state in various places to answer these questions in the software.  Events can get complex since they arrive in unpredictable patterns, generally speaking event handling code is defensive in nature.

The Chain Of Responsibility pattern is the following:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.  Chain the receiving objects and pass the request along the chain until an object handles it. 

An HSM consists of a set of state handler delegates.  Each state delegate is represented by a box in the diagram.  If a box is nested inside of another box it means that it lives in the CONTEXT of the outer box.  Everything that is true of the outer box is true of the inner box.  This is known as behavioral inheritance.  That is the core of the HSM system; you are inheriting behavior in the same manner  that you inherit type in traditional class inheritance.  Just like inheritance it is specialization in the sense that nested boxes only care about what is different from the outer box.

So now you can write event handlers that are chained together, you can focus on special cases in the context of more generalized event handling.  You can model what happened in the system in one cohesive location with a pattern that you can communicate to others.  Since declarative programming is all the rage these days you have a DSL ( Domain Specific Language ) for event handling. What's more important is the topology of the HSM tells a story in terms of modelling the context that the software is in.

HSM's have some interesting properties.  There is always an active box ( state / event handler ) that all events and commands are directed at.    If the state delegate consumes the event it does not bubble up the chain.  In this way state delegates are only concerned with how to hande events and commands in a particular context. 

If no box/state delegate consumes the event it bubbles off the top unhandled.  You can chose to take some action for certain events in this scenario depending on how important to your system an unhandled event of a certain type is.  You can think of each box as some variable in your branch logic that allows you to remember where you were or what you should do that under the circumstances ( context ). 

You have a reactive system and you are forced to code to some correctness specification.  An HSM allows you to do that – and to communicate your decisions to other programmers.  The alternative is a code review of the event handling logic of your program.  So I think of an HSM as modeling the branch logic of my event handling code. 

Each box as I mentioned is some variable somewhere that allows me to take the correct branch so I can reject deleting an address while it is being loaded.  Or saving something that is not valid; or thinking I am creating something new when it was loaded from the server; or allowing someone to edit an address that had timed out when it was being fetched. 

This is a behavioral pattern.  It's not structure ; and it's not workflow.  It doesn't model what I am supposed to do  next.  This is one of the hardest distinctions to make with HSM's because workflow or the process an object goes through frequently mirrors what can be done to it.  So try not to get confused ; always think of each box as a procession of variables in the branch logic of the event handler for a class.  It's not supposed to model the lifetime of the class or the steps involved in creating a new object, or editing and then saving.  

However it looks like that since those are the variables I would be checking against that tell me "I'm editing an address that was successfully fetched; the address is invalid so it's not ok to be saved; I should not allow delete either since the spec says we don't allow delete while in the edit state; that the user has to cancel out of that state frirst  then they can delete." 

Feel free to ask me about it.  Next is to model a simple shipping form – as you can see the canonical HSM is a simple CRUD model.  An exercise is to add the requirement that upon being fetched successfully from the server examine the timestamp of the last AVV and if it's older then spec item – XYZ – then revalidate the address.  Move the address into the proper state so that it 'reacts' intelligently depending upon the outcome of the validation.  Note that error's , timeouts, and the like can occur.

I can post a solution ( I think ) to this if anyone is interested.

Application Design

If the goal of the project is simple enough there is no need for a detailed design.  You can just design along the lines of responsibilities, make use of framework services and tack some ‘smart’ user interface onto whatever domain model you come up with.  By smart user interface I mean that it's OK to have business rules in the UI or that commands are embedded in with the view management code.

More complex enterprise applications start out with layered architectures.  Typically there are data services, entity frameworks and other services at the bottom layer(s).  A Domain Model in the middle, sometimes a thin abstraction layer for making the domain model consumable to the higher layers called a Service Gateway,  and the application and presentation layers at the top. Usually there is no difference between the presentation and the view of the presentation. 

Ignoring framework services like entity mappers , data repositories and Inversion Of Control containers, most of the complexity is in the Domain Model and the Presentation Surface.  Framework services if done right can be plugged in and possibly come from outside sources. You can use a facade together with adapters as long as the foreign code isn't so gross that it can't be contained.  This is code that once it is implemented correctly and maintained can be reused. The major design problem(s) are usually centered in the Domain Model (Domain Driven Design), the Presentation Surface(s) and how the various Views of the application are attached to those surfaces.

The typical object oriented design is from the bottom up.  After some decomposition of the problem space developers go about coding the individual classes.  There is sort of an article of faith that is common amongst code centric developers where the thinking is along the lines of ; "if the classes at the bottom have the methods and properties that model what I could possibly need from this class then I can wire the various objects together and make the code work".

Bottom up design is the wrong approach in a complex application and leads to applications that are not extensible and difficult to maintain.  This is because bottom up coupling is almost always too tight and for the most part there is a noticeable lack of design patterns in bottom up code. 

Think about building architecture, ugly buildings are built from pieces that are fit to make a whole.  Granted you do get a building by just fitting the pieces together. However there is no architectural flair or theme in buildings like that. They are just functional, not really architectural. For simple applications that don’t have a high need for change and extensibility this is fine.  Think of modular housing there is nothing wrong with it under the right circumstances.  But if your are going to go into the future with something that you want to be of very high quality because you are going to put your identity on the line as a business - you don't want to design from the bottom up.

In a complex application there are going to be patterns that emerge that are important for various reasons.  These patterns become apparent during the analysis phase.  There are the "ilities", extensibility, testability, flexibility, and aspects such as branding the user interface, variability in business rules that have to be plugged into a wide range of scenarios, the addition of new entities from the data services layer, etc.. When these requirements are stated up front the goal is to identify the key architectural patterns that would support them and to prove small pieces of the design in some type of adaptive coding process.  It's adaptive in the sense that it's very tough to design all the way down in a predictive fashion with software.  It's not like a building in that sense.  There are going to be ambiguities and some design insights come with small iterative attempts at realizing design goals.

The patterns that are identified as enabling your architecture are referred to as senior most design contexts. They are the theme of the architecture; the most important patterns from which all of the other pieces play a supporting role.  This is just the opposite of bottom up design from parts; indeed it is top down design where the realization of the design through patterns is the most important goal, not wiring parts together to make a whole.

For example if you decide that all of the business rules must be factored into separate classes.  One Rule One Place - behind an interface ( a new mantra ) .  Basically you have to wrap families of business rules behind interfaces and attach them to some aggregate in the code.  The business rule interfaces must be composed into algorithms in some way and the results have to be surfaced through some notification pattern. 

The resulting business rule interfaces must be interchangeable without the context classes that use them being aware of which variant they are executing.  You can build the business rule interfaces with IOC containers driven from external configuration files.  Still the architecture of how the business rule interfaces play in the domain model has to be designed properly. 

This kind of architecture has to come down from the top and has to drive the design of the system from the onset.  There is always gong to be some aspect that is the really tough part of the design.  It's where the ambiguities are and where there are not readily available models from other people that you can learn from.  Domain Modeling is kind of like that since it's your LOB ( line of business ) , other people can't teach you the domain.  The development of the initial code should realize some model of the high risk tough stuff as soon as possible.   

An example of a senior design context would be the decision to manage the dependencies of the application with an Inversion of Control container.  That would require that the system be programmed to interfaces and not classes. 

Let's take extensibility and flexibility and see what are the architectural patterns that can help us there.  For the most part these objectives are met with modern coding practices.  Primarily SRP ( Single Responsibility Principle ).  As far as patterns go there are at least two; Inversion Of Control containers which take over the creation aspects of how interfaces are assembled ( which motivates another practice design to interfaces and not classes ) and DM-VM-V ( DataModel , ViewModel, View ) which guides you to create user interface that is thin and can be changed easily. 

Testability is a design and project decision around which coding method you use and how unit tests are integrated into the solution.  Testability is affected by building a presentation surface and keeping the user interface thin since there is no state left in the view. It's much easier to unit test the presentation code since it's non-visual (you don't need tools to flex the GUI ).

Absent the requirements for easily changed and extended user interface you can merge the presentation and view layers. In case you are thinking this sounds like a bad design choice remember that all applications have a merged presentation and view, even Model View Controller applications. In MVC the View is the presentation; there is no separation into two layers. The model is the Domain Model, the controller is kind of like a presentation but it is not a surface for attaching a view. 

These are senior design contexts; they make the overall look of the software building that is under construction what it is.  It is making design decisions at the onset and then doing OOA/OOD decomposition.  It is coding form the top down and is well suited to agile and other adaptive coding practices.

Open Closed Principle

When there is new functionality or behavior added to a software system only new code should be added. In particular it should not be necessary to modify old code to add new behavior or features.  This basically says if you keep in mind the SRP (Single Responsibility Principle) you should have a loosely coupled system.  You wind up with more interfaces with clearer responsibilities.  Systems like this are easy to extend because when you don't find code in the same location that is doing lots of different things.  The open / closed principle which is supported by SRP is a big driver behind the notion of aggregates and the dependencies that result from them.  It has a side effect of making deep dependency graphs which brings complexity back into the design of another sort. However there is a wonderful mechanism for managing the complexity of dependency. Inversion of Control containers are so vital to the realization of extensible software because they allow you to adhere to SRP and make these complex aggregates that are driven from external configuration files.

Find What Varies and Encapsulate It

This tends to cut down on inheritance hierarchies and goes against the objects as smart data paradigm. Good design is partially about the identification of variance (Commonality Analysis). Choosing the proper design for encapsulating variance behind an interface shields the rest of the system. This increases the clarity of the design.

Object Composition vs Inheritance

Whenever there is inheritance there is some specialization that is driven from variability that is occurring. Frequently the variance can be grouped together and factored into a separate implementation hierarchy.

This avoids inheritance in the core of an aggregate and shifts the deferred inheritance to an adjunct subordinate class.  Evans (Domain Driven Design Quickly) states that classes that describe, provide, or help the main concept classes are value classes.  That description is a little vague but these classes do not represent top level concepts in the design, they are not the nouns in the problem domain. Classes that help the core of aggregates are the products of factorization and implement various interfaces in the service of the core concept.   

These helper class hierarchies are much less important in the design as there is a net gain by keeping the inheritance of the core to a minimum.  We would rather have a class hierarchy in a dependency class then the root of an aggregate that is a main part of the design.

This is known as a Bridge pattern and it helps with the Single Responsibility Principle. It keeps the core class focused on particular responsibilities which results in looser coupling. Be very stingy with the use of inheritance when you need to handle a special case in the code.   

Promote High Cohesion

Cohesion is a measure of how tightly focused the code is. Another way of looking at it is how closely the operations in a routine are related. Systems that are highly cohesive are loosely coupled. Loosely coupled systems allow you to introduce new behaviors without changing unrelated code. Classes should be well defined and focused on specific roles in the design – and nothing else. Cohesive classes are only about one thing.

Single Responsibility Principle

Highly cohesive systems obey the Single Responsibility Principle or SRP for short. When classes get to big and do too many things break them apart along lines of responsibility. Use interfaces to group the responsibilities into readily identifiable chunks.

This leads to software designs that consist of groups of Aggregates since a core abstraction is now served by subordinate classes implementing various interfaces. An Aggregate is a software structure that has a root class with dependencies on provider, descriptive or helper classes. These classes are wired together with dependencies either through the constructor or property setters. In the domain layer of a design the root of aggregate structures is usually an Entity (Entities have identity and are from the data services). In the presentation layer the root of an aggregate would be a View Model which is the central abstraction for providing a surface to bind a View onto. In either case the construction of the aggregates should be done with the help of an Inversion of Control container (factory on steroids).

Identify Patterns

The purpose of patterns is to define relationships among the concepts in the problem domain. Think about a software system in terms of the patterns that are present. The patterns are not as important as the relationships but give us a way to talk about them.

Start With Context Patterns

Identify the patterns that create the context for the other patterns. These should be your starting point. They represent the 'drivers' in your design. Fundamental design decisions you have made at the beginning that should shape the overall structure of the code.

Work Inward From the Context

Look at the remaining patterns and at any other patterns that you might have uncovered. From this set, pick the patterns that define the context for the patterns that would remain. Repeat.

Refine the Design

As you refine, always consider the context implied by the patterns. Do not forget your previous design decisions and introduce new ways of doing things.

Context: Separation of Behavior from Structure

Design a system to handle varying, but related, business rules, algorithms and policies. The system should be externally configurable as to the particular business rules, algorithms and policies that apply in a specific situation. Each algorithm, business rule and policy should be defined to be in a family containing all variations with a common interface. Commonality and variability analysis can be used to design the interface(s).

The use of Inversion Of Control container factories allow us to configure the exact class that implements a given strategy interface through generic specialization. It is possible to use composition to build complex strategies that handle sets of business rules with increasing levels of semantic relevance to the design.

Context: Use an Inversion of Control Container

One of the biggest issues in software systems today is managing the dependencies between objects. When we build aggregates we are using delegation to subordinate classes in order to realize the implementation of interface that serve the core responsibility of the aggregate.

Basically we have dependencies on classes that the core delegates too; these classes each have their own dependencies and the resulting graphs can get deep (think transitive closure).

Unmanaged, dependencies can get out of control without you even noticing. You notice when you change a constructor for some class the code breaks in enough spots so that it is a major problem.  It can be hard to instantiate an object because it needs some dependency that might be a number of levels down.  IOC containers help you with those types of problems, they make the code so much more flexible and extensible.

Inversion of Control and Dependency Injection are two related ways to break apart dependencies in your applications. They are explained in detail by Martin Fowler in his article Inversion of Control Containers and the Dependency Injection Pattern, but a few lines are in order.

Basically Inversion of Control means that you are not writing the code to couple your classes together when you need to produce an aggregate. You are requesting an interface from the framework in this case the container factory possibly supplying some arguments that allow the IOC container to construct the interface. More importantly the dependencies are coupled together inside of the container either through the constructor or by setting properties ; this is known as Dependency Injection.

The control is inverted because the code for wiring up the various objects is typically a key and important part of a software system. The precise classes that implement interfaces are ‘baked into the cake’, they cannot be changed, the dependencies are hard coded. The use of an IOC container changes all of that around, now the specifics are in external configuration files, the container is the only part of the system that knows the exact class type that implements an interface. In this sense the programmer is no longer in control of the classes and how they are coupled together. The framework is in control via the IOC container.

There is another aspect to using an IOC container. That is you can think of the code as composed of code that is implementing the how of the responsibility and code that is implementing the responsibility. Basically when I use an IOC container then how I am building an interface and satisfying dependencies is removed, leaving the code that is using the interface. The result is more cohesive as a whole.

The other interesting aspect is when additional responsibilities are added to an interface without the client knowing about it. There is a great example of using decorators to send email from a repository that you can read about here.

Context: Headless Application

Support multiple views that can be quickly developed and changed without impacting other layers of the application. This notion of a 'skin' that can be removed with a minimum of impact to the rest of the application is (finally) possible with .Net 3.5 and Windows Presentation Foundation. This type of design requires very extensive support for data binding along with an enhanced command system and routed events that tunnel as well as bubble through the view hierarchy.

Use the composite pattern to create complex presentations from simpler ones that support subsections of the user interface. Complex screens can be implemented in manageable pieces and reused in other views. 

The new pattern for user interface is much more powerful then the familiar Model-View-Controller. There is a design that has been proposed at Microsoft known DataModel / ViewModel / View that promotes the use of thin views that can be removed without hurting the rest of the application.

This enables the development of applications that have a set of explicitly defined 'surface' or attach points that contain presentation logic for various 'views'. The DM-VM-V pattern keeps the user interface logic out of the view layer and in the presentation surface. This enables testing of most of the view responsibility through normal unit testing methods since behavior and state is kept in the on visual presentation. This is a major advance in terms of testing the user interface of a modern application since tools that exercise visual control are much more involved than running unit tests.

A typical presentation surface consists of an aggregate whose root class is known as a ViewModel or Presenter. The core is an aggregate that presents a binding surface for any data that is visualized in the view along with any properties that are exposed through controls. This binding surface should be in the form of an explicit interface that is implemented through delegation since the responsibility of the core is primarily composition.

The other typically important interface is the command interface for a presentation model and how commands are invoked from the view and how command arguments are passed through to the presentation. The Command object itself is part of the presentation not the view layer. However arguments are accessed through data binding in a similar fashion as data and properties. There is also an interface that is queried by the view layer to determine if the command is available to be executed.

There is no rule concerning how many View Model surfaces are required to support a given view. In actuality there is a at least one ViewModel for every dialog and or window. In a complex main view a composite of a root ViewModel composed of child ViewModels can be employed to support complex view hierarchies. View hierarchy is a common design in GUI code while using composition to make a mirrored presentation surface is a relatively new design concept that has emerged with the desire to create super thin ‘dumb’ view layers that can be changed and or replaced entirely without affecting the application logic.

Supporting "Skins" Or Keeping the Presentation Logic in the ViewModel and out of the View

The ability to have multiple looks or views on the same non visual presentation surface relies on careful enforcement of a key programming goal. That is presentation logic must be kept outside of the View classes. There can be no state or commands semantics mixed in with the View code.

In normal application programming the presentation code is part of the view logic. Since there is no clear separation of concerns once the view logic has been removed the application is missing most if not all of the presentation state. Essentially you have no working application outside of the view layer.

An example in .Net of mixing presentation logic with view logic is illustrated by the use of code-behind files in WPF XAML. Most of the code in the code behind sections of the XAML represents presentation logic; command delegates are a common example.

Serialization And Coupling

Xml Serialization is loose coupling when it comes to type fidelity. If you can accept this definition of loose coupling as "things which behave independently of each other or at least explicitly state what the relationship is", then the Xml flavor of object serialization is loose as opposed to Binary or Soap serialization which is tight. I mention this because without going into details I had this misconception that the code for performing serialization on the server had to be the same; e.g. the same assembly, as the code doing the de-serialization on the client. This is not true and in fact there is so much control in the case of XmlSerialization at least that given a piece of Xml it should be very easy to engineer a type that matches it and allows you to come up with an Adapter class that deserializes the Xml into a .Net CLR type. This is extremely useful since with serialization you aren't writing code to manipulate an Xml Dom. That is so old hat; the .Net FCL gives us much better tools with the 3.0 serialization upgrades.

In the spirit of Xml there is no enforcement of the object's data type with respect to matching assemblies on the part of the serializing agent and the de-serializing agent. For instance with Binary Serialization the Binary Formatter persists not only the field data of the object(s) but also each types fully qualified name and the fully qualified name of the defining assembly (name, version, public key token, and culture). The SoapFormatter persists traces of the assembly of origin through the use of an Xml namespace.

However the XmlSerializer does not attempt to preserve the full type fidelity and therefore does not record the type's fully qualified name or assembly of origin. The reason has to do with the open-ended nature of Xml data representation. After all Xml is intended to be cross platform data so insisting on type fidelity during serialization through something like an assembly match doesn't make sense; after all you might be sending data to a Mac or a Linux box ( if their lucky they are using .Net with MONO but that is another topic ).

This is a good thing if you are using serialization for CLR type security and Xml for data interchange and you are worried about having the data be tightly coupled to a particular flavor of the code; e.g. you would have to release a revision to the code in order to change anything about the data; or even worse you updated the code and the old data was suddenly incompatible. Relax J XmlSerialization is F-L-E-X-I-B-L-E.

What I mean by that is if you are writing an application you want a lot of cohesion in your data.  You should basically be striving for POX (plain old XML ) data types. Resist the temptation to load up your data classes with code and properties that aren't part of the data but are intended to support the application.  That is what Model Classes are for!!! So you transferring data as XML Elements should be adquate in the vast majority of cases.  Failling that I can't imagine a scenario where you could not write an Adapter class that conforms to the Xml; say you get some existing XML and you want to make your application compatible with it AND you want to convert this into 'normalized' data.  You have an existing data class that is serialized; so now you use the Adapter pattern and then use Aggregation with the compatible existing type and the rest of the application treats the data normally.  So rippling changes up through a lot of code does not have to take place even if the data changes considerably in the future. 

WPF has this really convenient class called XMLDataProvider that allows you to use XPath to bind UI to Xml.  This is really nice but only for read-only data.  You can Bind a WPF View two ways with XMLDataProvider and edit the Xml but it's not conformant enough for a production piece of user interface since it's hard to validate inputs. Contrast this with something like a .Net DependencyProperty with meta data and a CoerceValue callback and you will see two very distinct poles of data integrity.

Also note that .Net gives you a lot of tools to control the Serialization/De-Serialization process in the System.Xml.Serialization namespace. Specifically check these attributes out.

XmlAttribute

The member will be serialized as an Xml attribute.

XmlElement

The member will be serialized as an Xml element.

XmlEnum

The element name of an enumeration member.

XmlRoot

Controls element name and namespace of root.

XmlText

Serialize the property of field as Xml text.

XmlType

The name and namespace of the Xml type.

 

Remember only public fields and their associated backing fields ( if any ) are serialized with the XmlFormatter. Now check out this cool stuff when you are interested in serializing a large collection like an Address Book and you want to show detailed progress.

[OnSerialized]

This attribute allows you to specify a method that will be called immediately after the object has been serialized.

[OnSerializing]

This attribute allows you to specify a method that will be called before the serialization process.

[OnDeserialized]

This attribute allows you to specify a method that will be called immediately after the object has been deserialized.

[OnDeserializing]

This attribute allows you to specify a method that will be called before the deserialization process.

[OptionalField]

This allows you to define a field on the CLR type that can be missing from the specified stream.

 

Software Architecture

Hi: I'm just starting my blog. On that note I'll just begin with a stream of consciousness on client side architecture. Florian Krush's Kool Xaml Blog is a great place to catch up on some cutting edge .Net ideas especially if you are interested in WPF and client stuff. Speaking of WPF based clients there aren't very many to choose from at the moment. Yahoo Messenger is a notable exception and apparently much but not all of the Microsoft Expression product line uses WPF.

One of the lead developers is a fellow named Dan Crevier. Basically most of us programmers are aware (the client side ones at least ) of MVC Model View Controller as one of the basic architectures for writing GUI code. I won't go into details here but it's about having layers and a separation of concerns ( isn't it always ) with the recurring theme of not mixing the code up so much that it's hard to maintain and impossible to change. With WPF the V part (view) is inherently factored if you are using XAML and data binding. The reason why is you are no longer writing imperative code ( plain old code ) and instead using a DSL Domain Specific Language to specify the View part of an application. While this starts you down the road of writing UI (ignore the design part for now, just focus on arch ) in a modern fashion it by itself will not provide enough arch to keep you out of trouble. However when you use XAML there is frequently code in code behind files to support the WPF objects in the view. The presence of the code behind files serves as a de-facto ViewModel in that it's the controller logic for your View in many cases. For example it's common to have Command Bindings in the window's code behind file. This is a layer violation so now you can't test the application without the View code. In fact you have no application without the View. In other words you are mixing the model and the view code together in the code behind file(s).

This is an excerpt from Martin Fowler's Supervising Controller piece: Supervising Controller

Many UI frameworks provide the ability to easily map between the view and model, often using some kind of Data Binding. These approaches are very effective in allowing you to declaratively set up a relationship between elements in the view and model. Usually, however, there are more complex relationships which require you to have more complex view logic. This logic can be hard to manage, and in particular hard to test, while embedded in the view.

So now enter Dan Crevier and his excellent posts on DM-VM-V allow you to design your way out of code behind files. If you are interested in client arch this is a great place to start especially if you are using WPF.   http://blogs.msdn.com/dancre/archive/tags/DM-V-VM/default.aspx

All of this is kind of driving towards this goal of the headless application that can be tested exclusively of the user interface. Contrast this with buying test automation tools that actually drive the user interface from scripts. That is the old way of doing things.

 
There are no photo albums.

markb

Occupation
Location
Interests
I graduated from UCSC in Computer Science class of 1991. I've been a software developer on various projects ever since. I have worked in large and small companies. Notably Apple, ConnectSoft, WRQ and iShip Inc.

You can contact me at tibby99@gmail.com