Archive for May, 2009

Big (D)esign Conference 2009

Saturday, May 30th, 2009

The Big (D)esign Conference was an upbeat collaboration among designers, developers, and strategists. The common theme was finding ways to improve the quality of peoples lives through software. That's a theme that resonates with me, as I have made that my personal and professional mission.

This was a brand new conference. As such, there were some minor logistical problems, like a scarcity of water. But the attendees took to Twitter and the organizers responded. By the end of lunch, water bottles were abundant.

That's just one example of the dynamic nature of this conference. It wasn't about people expecting a solution to be handed to them. It was about people finding solutions, responding to needs, and making the conference a success. The organizers did a great job, but it was the attitude of the attendees -- who really made the conference their own -- that made this day a success.

The Twitter hash tag was in full force during the conference. Many attendees Twittered salient points of the presentations, their own opinions of the topics, or just their sandwich selection. The Twitter stream was as lively a conversation as any occurring in the hall. Through this conversation, I made several new friends.

In addition to making new friends, I ran into some old ones. One of my old friends, T. Scott, is a designer. He recorded his notes and uploaded them for all to enjoy. So I decided to do the same, in my own way. My writing is not so beautiful, so here is a transcription of my notes from the sessions I attended.

Keynote: Norm Cox, Interaction Therapist
Titles are limiting. When we think of ourselves as developer, designer, business analyst, or architect, we tell ourselves (and others) that that is all we do. We limit our own thinking.

We should instead become jacks of many trades, masters of some. Know all of the disciplines related to solving someone's problem. Know which ones you are particularly good at, but don't let your skill set limit your solution.

Clients don't know what they need. Don't do exactly as they tell you to. Be a therapist. See the big picture. Find the real problem. Don't limit yourself to the client's expectations.

Chris Koenig: Touch Computing
A Natural User Interface (NUI) is touch, gesture, manipulation, and more. It doesn't replace the keyboard or the mouse. It is designed for different scenarios. And sometimes, it augments them.

NUI is good for free-form exploration and social interaction. It is not good for precision. It is not good for data density. Those are best left to the keyboard and mouse.

Touch is not click. Don't take old metaphors and idioms and apply them to NUI. This is a new space, with a new set of benefits. Add it to your tool belt, but don't replace your trusted tools.

Stephen P Anderson: The Art and Science of Seductive Interactions
Good usability lowers friction. But that's not enough. You also need to increase motivation. That can only be achieved by understanding psychology.

People respond to levels and rewards. LinkedIn rates the completeness of your profile, and tells you what to do to take it to the next level. That motivates people to behave the way LinkedIn wants. This behavior benefits LinkedIn, but it also benefits the individual.

Seduction is deliberately enticing a person to perform a desired behavior. It requires feedback. The person needs to see that their actions modify the results. Their curiosity will lead them to try other behaviors and see how the results change.

People respond to visual imagery. They look for patterns in everything they see. They recognize, even if they can't recall. Software that uses these traits to its advantage will successfully guide users to perform the desired behavior. So show people images and have them find patterns that give you information about them. Don't give them a blank slate and ask them to recall that information.

Bill Scott, design patterns you should know
Nuance is where a lot of good interaction is missed. Pay attention to the details to eliminate friction.

Make it direct. Where there is output, let there also be input. Let interactions be symmetric; don't enter a feature one way and leave it another.

Make it lightweight. Interactions have a "click-weight". Too many interactions, and your users are going to get tired and go away.

Avoid confusing and annoying anti-patterns. Don't hover-and-cover, popping up new content over old content, or hiding navigation. Avoid "mystery meat" navigation, when the user doesn't know what a click is going to do until they do it. Follow Fitts' Law and give the users a big enough target.

Stay on the page. Use overlays (pop-out) and inlays (slide-down) wisely. Don't stop the proceedings with idiocy. Use carousels, endless scrolling, and deep zoom where appropriate.

Offer an invitation. Play on your user's curiosity. Give them a call to action. Show them the steps involved. Walk them through. Make it discoverable.

Show transitions. Fade in and out. Animate zoom and motion to draw people to the next state.

Garrett Dimon, Feedback and Feature Requests
The creator of Sifter talks about how he solicits feedback from his customers, and how he rolls that in to new features of the product.

The lifecycle of feedback is: generate, receive, reply, absorb, and respond. Generate feedback by actively soliciting it from your users. Receive it in the form of analytics, web forms, email, phone calls, and anything else that is accessible to your users. Reply quickly to tell them whether you're looking into it, or if not why. Take the time to absorb the feature request and consider its full impact. Then respond by implementing the feature, a different feature, or giving the customer the results of your full consideration.

Don't be afraid to be transparent in your product development process. People will not steal your ideas. If your idea is really great, you are going to have to shove it down their throats.

If it needs instructions, then it's broken. Instead of explaining to people how to use your software, fix the problem. If you have Frequently Asked Questions, you have feedback. Fix it.

The proportion of criticism to encouragement is absurd. It will bring you down. But always be upbeat when you reply. It will turn the conversation around and open a constructive dialog.

Always think about the need, not the prescription. Find out what problem people have. Don't give them the solution they ask for. Don't abdicate software design to the users. Listen to them, then deliver a solution.

There are no takebacks with features. Consider whether it really belongs in your product before adding it. It may delight some people but confuse others. If you add it, you will have to support it for the life of the product.

Todd Wilkens, Saying "No" and Failing
Saying "No" limits the scope and gives you clear focus. First evaluate features based on importance and feasibility. Implement the ones that are important and feasible. Consider the ones in the middle. Ignore the ones at the low end of the scales.

Design is about specific customer value. Start with the things that you can do exceedingly well, and deliver that value completely. Each evolutionary step after that should provide complete customer value for a new problem, not a new part of an incomplete solution.

Failure is always an option. Everybody fails often. Just make sure you are failing forward. Fail early and often, and learn from each iteration.

If failure is not allowed, then risk is not allowed. The companies that succeed openly encourage failure internally. Separate personal feelings from ideas, so that the ideas can fail without damaging the people.

Have a conversation with the materials. Through the process of making something, we change the thing that we are making in response to the stuff that we are making it out of. Create an informal iterative process for failing often.

Thor Muller, Work Like the Network
Challenge the assumption of industry: that it is expensive to coordinate a large number of people to build something. When we move electrons instead of molecules, those economics no longer hold. The use of a physical product reduces its value. The use of an idea increases its value.

There are six ways to remodel the organization to act more like a network.

Move from To
Control Chaos
Process Flow
Documentation Collaboration
Fear Confidence
Hidden Shown
Ownership Stewardship

It was a relief to hear someone talk about agile processes and flow without being dogmatic about it. Thor just demonstrated the value of these principles, and showed how organizations have applied them successfully.

Christina Wasson, When Design Met Anthropology
Ethnographic anthropology is the study of cultures by becoming an observant participant. It is a systematic approach involving capturing information, coding it, and analyzing it. These techniques can be applied to software to give us a scientific basis for measuring the quality of a design.

Ironically, the case study that Christina shared was juxtaposed against the spirit of the conference. a study of Social TV, a Motorola project to join families in different living rooms around the viewing experience. The case study was punctuated by videos of people talking about how they negotiate over the remote, how television fits in with their lives, and how their family members react. It was depressing to see people still engaging in a one-sided conversation with old media, instead of using their talents to create something awesome.

I'm excited to have met such a fantastic group of people. I look forward to working with them to improve the quality of peoples lives through software. I'm looking forward to checking in next year to see how we've done.

SQL injection attack via email address

Thursday, May 28th, 2009

Here's a perfect example of the wrong way to defend against SQL injection attacks.

I am trying to obtain a license for NDepend for use in my open source project Update Controls. Their download form asks for an email address. So I provided the email address I use for that project. The form didn't like it:

SQLinjection

XKCD warns us to "sanitize our database inputs". That is completely wrong. Honor the user's input, but treat it as text, not code.

A View Model based approach to star ratings

Thursday, May 28th, 2009

While researching for Commuter, I found several implementations of star-rating controls on the web. Here are a few:

There's some good work here, so give them a read. However, a common theme was to control the stars with code. I wanted to do a purely View Model driven design to limit the amount of code necessary.

The View Model is dependent behavior
Commuter View The first thing to realize is that a star rating control is a combination of independent and dependent behavior. While the rating itself is independent, the state of each star is dependent. The independent data lives in the model, and the dependent data lives in the View Model. So I changed the podcast View Model contract to this:

public interface IPodcast
{
    string Name { get; }
    bool Selected { get; set; }
    bool Rank1 { get; set; }
    bool Rank2 { get; set; }
    bool Rank3 { get; set; }
    bool Rank4 { get; set; }
    bool Rank5 { get; set; }
}

This lets me just drop five ordinary check boxes and bind them to the five properties. Well, not ordinary check boxes. Mine look like cars. Here's a step-by-step guide for styling a control. Use the same technique to create star checkboxes, if you are so inclined.

Implement the View Model contract
A star -- er a car -- is on if the rating is greater than or equal to its value. When a car is clicked, it sets the rank to its value. This logic is encoded in the View Model like this:

public class PodcastViewModel : IPodcast
{
    private Podcast _model;

    public PodcastViewModel(Podcast model)
    {
        _model = model;
    }

    public string Name
    {
        get { return _model.Name; }
        set { _model.Name = value; }
    }

    public bool Selected
    {
        get { return _model.Selected; }
        set { _model.Selected = value; }
    }

    public bool Rank1
    {
        get { return _model.Rank >= 1; }
        set { _model.Rank = 1; }
    }
    public bool Rank2
    {
        get { return _model.Rank >= 2; }
        set { _model.Rank = 2; }
    }
    public bool Rank3
    {
        get { return _model.Rank >= 3; }
        set { _model.Rank = 3; }
    }
    public bool Rank4
    {
        get { return _model.Rank >= 4; }
        set { _model.Rank = 4; }
    }
    public bool Rank5
    {
        get { return _model.Rank >= 5; }
        set { _model.Rank = 5; }
    }
}

The View Model does not maintain any state of its own. It delegates everything to the model. It is just a translator that converts model values into view behaviors.

Implement INotifyPropertyChanged
Oh, wait! You don't need to do that. Just use Independent sentries in your data model:

public class Podcast
{
    private string _name = string.Empty;
    private bool _selected = false;
    private int _rank = 1;

    #region Independent properties
    // Generated by Update Controls --------------------------------
    private Independent _indName = new Independent();
    private Independent _indSelected = new Independent();
    private Independent _indRank = new Independent();

    public string Name
    {
        get { _indName.OnGet(); return _name; }
        set { _indName.OnSet(); _name = value; }
    }

    public bool Selected
    {
        get { _indSelected.OnGet(); return _selected; }
        set { _indSelected.OnSet(); _selected = value; }
    }

    public int Rank
    {
        get { _indRank.OnGet(); return _rank; }
        set { _indRank.OnSet(); _rank = value; }
    }
    // End generated code --------------------------------
    #endregion
}

And then wrap the view model for the view (requires Update Controls release 2.0.4.0 or better):

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        DataContext = ForView.Wrap(new CommuterViewModel(new CommuterModel()));
    }
}

Now when you click on any of the cars, it will set the Rank property in the data model. Since all of the check boxes depend upon that one Rank, they are all updated accordingly.

Queries are dependent; records are independent

Tuesday, May 26th, 2009

I just requested a change to a use case that will save the project two man-weeks.

eCommerce users and accounts
In the ABSG eCommerce system, a user shops on behalf of an account. Some users can shop on behalf of more than one account, so they get to choose at the beginning of their shopping experience. We populate their list of choices from all of the accounts to which they have access.

Employees of ABSG log on to the eCommerce system to help people. Some of these employees are sales representatives for certain accounts. Every account as one sales rep.

Not all users are employees, and not all employees are users. We enter an employee ID in the user profile to relate the two.

The UserAccount table
UserAccountAccessERD We have an associative table that relates users with the accounts that they have access to. This is the table that we use to present the list of choices at the beginning of the user's shopping experience.

I'm sure at one point the BA asked me how a user is granted access to an account. I told her about the UserAccount table. It was probably that conversation that led to the Automatic Account Access use case.

Automatic account access
Use case 1019: Automatic Account Access stated that the system shall periodically check for changes:

  • An account is created or deleted.
  • A user is created or deleted.
  • The sales representative for an account is changed to a different employee.
  • The employee ID of a user is changed.

When one of these changes happens, it will:

  • Identify the sales rep user for each affected account.
  • If there is no existing UserAccount record for that sales rep, insert it.
  • If there is an existing UserAccount record for a prior sales rep, delete it.

This to be accomplished via an impressive combination of replication, triggers, and cursors.

The problems
There are a number of problems with this approach.

  • It would take a DBA a couple of weeks to build and test it.
  • Testing cannot be easily automated.
  • It has too many moving parts (a.k.a. degrees of freedom), each one of which would have to be monitored.
  • It is difficult to determine whether a UserAccount record was created automatically or intentionally. We could either delete UserAccount records that a human created, or fail to delete UserAccount records that a program created.
  • It does not take effect immediately. People could be diagnosing a failure of the system, only to learn that the process hasn't yet run.

These are all symptomatic problems. Each one could be addressed individually. But here are the two big systemic problems:

  • The use case dictated a solution.
  • The solution treated dependent behavior as independent behavior.

Here's my solution
Use case 1019 has been deleted, and replaced with a single business rule in use case 1020: User Account Selection. This rule reads "A user may select any account for which their employee ID matches the accounts sales representative employee ID." No mention of how this is accomplished. That solves one problem.

To solve the other, we simply change the query that fetches the list of accounts at the beginning of the shopping experience. Instead of just getting UserAccount records, we also include accounts where account.SalesRepEmployeeID = user.EmployeeID. One small change to a query, instead of a rats nest of triggers.

The basic flaw in the Rube Goldberg reasoning that produced the original design was that it treated dependent behavior (which accounts the user could select) as independent behavior (UserAccount records). Changes to independent behavior (INSERTs, UPDATEs, and DELETEs) are permanent. Changes to dependent behavior (SELECTs) are not. It is therefore harder to code, test, monitor, and troubleshoot independent behavior. It is easier to do all of those things with dependent behavior.

Records are independent. Queries are dependent. Never use independent behavior when dependent behavior is sufficient.

How to draw a car in Expression Blend

Sunday, May 24th, 2009

For the Commuter application, I want to rate podcasts. But instead of giving them a star rating, I decided to give them a "car" rating.

My inspiration I drive a blue Beetle, so naturally I decided that that would make the perfect template for my rating buttons. Here's how to draw a Beetle in Expression Blend.

Start with two circles for tires, and two circles for wheel wells. Choose the ellipse tool, and hold Shift while you drag from one corner to the opposite. Draw a small circle inside a big circle, then select both, hold Ctrl, and drag to create copies.

Wheels and wheel wells

Next, draw an ellipse for the body. Don't hold Shift while you drag, so the shape is unconstrained. The ellipse goes through the center of the front wheel, and is tangent with the back wheel. Switch to the rectangle tool to add a rounded rectangle for the bottom of the car.

Body and trim

Now we're going to use "Combine" operations to trim the shapes. When you combine two shapes, the originals are lost. We want to combine the rounded rectangle with several shapes, so we need to make copies of it.

The first operation will be to cut one wheel well with the rounded rectangle. Make a copy of the rectangle, select both it and one wheel well, and select "Combine: Intersect" from the "Object" menu. Repeat for the other wheel well, and for the body.

Trim all parts

We are done with the rounded rectangle, so we can delete the original. Next, select the two wheel wells and the body and "Combine: Unite" them. There will be a strange dip where the front wheel well meets the windshield. We'll straighten this out by adding a rectangle.

Flatten the dip 

Unite the rectangle with the outline of the car to complete the picture.

Finished path

Now we can use this path as the basis for rating buttons.

Data Binding in Silverlight 3 Without INotifyPropertyChanged

Wednesday, May 20th, 2009

Update Controls for Silverlight 3 is in beta. Update Controls automatically discovers dependencies upon your data model, and updates the UI when that data changes. It does not require that you register for or fire PropertyChanged events.

Step 1: Write your data model
Your data model is a set of plain-old CLR objects (POCOs). These objects have fields that store data, and properties that expose that data to consumers. They may also have validation logic, business logic, and other methods. The data model will be persisted, probably via a web service.

public class Customer
{
	private string _name;
	private List<Invoice> _invoices = new List<Invoice>();

	public string Name
	{
		get { return _name; }
		set { _name = value; }
	}

	public IEnumerable<Invoice> Invoices
	{
		get { return _invoices; }
	}

	public Invoice NewInvoice(string number)
	{
		Invoice invoice = new Invoice(number);
		_invoices.Add(invoice);
		return invoice;
	}
}

public class Payment
{
	private Customer _customer;
	private List<Invoice> _paidInvoices = new List<Invoice>();

	public Payment(Customer customer)
	{
		_customer = customer;
	}

	public Customer Customer
	{
		get { return _customer; }
	}

	public IEnumerable<Invoice> PaidInvoices
	{
		get { return _paidInvoices; }
	}

	public void AddPaidInvoice(Invoice invoice)
	{
		_paidInvoices.Add(invoice);
	}

	public void RemovePaidInvoice(Invoice invoice)
	{
		_paidInvoices.Remove(invoice);
	}
}

Step 2: Add Independent sentries
Add a reference to UpdateControls.Light.dll. Then create an Independent sentry for each field in your data model that can change. Call OnGet whenever you read the field, and OnSet whenever you change it.

public class Customer
{
	private string _name;
	private List<Invoice> _invoices = new List<Invoice>();

	private Independent _indName = new Independent();
	private Independent _indInvoices = new Independent();

	public string Name
	{
		get { _indName.OnGet(); return _name; }
		set { _indName.OnSet(); _name = value; }
	}

	public IEnumerable<Invoice> Invoices
	{
		get { _indInvoices.OnGet(); return _invoices; }
	}

	public Invoice NewInvoice(string number)
	{
		_indInvoices.OnSet();
		Invoice invoice = new Invoice(number);
		_invoices.Add(invoice);
		return invoice;
	}
}

public class Payment
{
	private Customer _customer;
	private List<Invoice> _paidInvoices = new List<Invoice>();

	private Independent _indPaidInvoices = new Independent();

	public Payment(Customer customer)
	{
		_customer = customer;
	}

	public Customer Customer
	{
		get { return _customer; }
	}

	public IEnumerable<Invoice> PaidInvoices
	{
		get { _indPaidInvoices.OnGet(); return _paidInvoices; }
	}

	public void AddPaidInvoice(Invoice invoice)
	{
		_indPaidInvoices.OnSet();
		_paidInvoices.Add(invoice);
	}

	public void RemovePaidInvoice(Invoice invoice)
	{
		_indPaidInvoices.OnSet();
		_paidInvoices.Remove(invoice);
	}
}

Step 3: Write your navigation model
The navigation model keeps track of all of the user's selection. It helps the user navigate through the data model. Use Independent sentries, just like you did for the data model. But unlike the data model, the navigation model is not persistent.

public class PaymentNavigationModel
{
	private Invoice _selectedUnpaidInvoice;
	private Invoice _selectedPaidInvoice;

	private Independent _indSelectedUnpaidInvoice = new Independent();
	private Independent _indSelectedPaidInvoice = new Independent();

	public Invoice SelectedUnpaidInvoice
	{
		get { _indSelectedUnpaidInvoice.OnGet(); return _selectedUnpaidInvoice; }
		set { _indSelectedUnpaidInvoice.OnSet(); _selectedUnpaidInvoice = value; }
	}

	public Invoice SelectedPaidInvoice
	{
		get { _indSelectedPaidInvoice.OnGet(); return _selectedPaidInvoice; }
		set { _indSelectedPaidInvoice.OnSet(); _selectedPaidInvoice = value; }
	}
}

Step 4: Write your view model
The view model interprets the data model for the view. It has no data of its own. It just references the data model and the navigation model. It has a property for every bindable control in the view. It also has an ICommand property for every action the user can perform.

public class PaymentViewModel
{
	private Payment _payment;
	private PaymentNavigationModel _navigation;

	public PaymentViewModel(Payment payment, PaymentNavigationModel navigation)
	{
		_payment = payment;
		_navigation = navigation;
	}

	public string CustomerName
	{
		get { return _payment.Customer.Name; }
		set { _payment.Customer.Name = value; }
	}

	public IEnumerable<Invoice> PaidInvoices
	{
		get { return _payment.PaidInvoices; }
	}

	public IEnumerable<Invoice> UnpaidInvoices
	{
		get { return _payment.Customer.Invoices.Except(_payment.PaidInvoices); }
	}

	public Invoice SelectedPaidInvoice
	{
		get { return _navigation.SelectedPaidInvoice; }
		set { _navigation.SelectedPaidInvoice = value; }
	}

	public Invoice SelectedUnpaidInvoice
	{
		get { return _navigation.SelectedUnpaidInvoice; }
		set { _navigation.SelectedUnpaidInvoice = value; }
	}

	public ICommand AddPaidInvoices
	{
		get
		{
			return MakeCommand
				.When(() => _navigation.SelectedUnpaidInvoice != null)
				.Do(() =>
				{
					_payment.AddPaidInvoice(_navigation.SelectedUnpaidInvoice);
					_navigation.SelectedPaidInvoice = _navigation.SelectedUnpaidInvoice;
					_navigation.SelectedUnpaidInvoice = null;
				});
		}
	}

	public ICommand RemovePaidInvoices
	{
		get
		{
			return MakeCommand
				.When(() => _navigation.SelectedPaidInvoice != null)
				.Do(() =>
				{
					_payment.RemovePaidInvoice(_navigation.SelectedPaidInvoice);
					_navigation.SelectedUnpaidInvoice = _navigation.SelectedPaidInvoice;
					_navigation.SelectedPaidInvoice = null;
				});
		}
	}
}

Step 5: Set the data context
Now comes the magic. At no point did you fire or register for PropertyChanged events. You just wrote code that was concerned with the rules of your business (data model) and your user interface (view model and navigation model). Update Controls will fire those PropertyChanged events for you, if you wrap your view model before giving it to the view:

Customer customer = new Customer();
// Load the customer from the web service.

Payment payment = new Payment(customer);
// Load the payment from the web service.

DataContext = ForView.Wrap(
	new PaymentViewModel(
		payment,
		new PaymentNavigationModel()));

Update Controls makes it much easier to create line-of-business applications in Silverlight 3, because you don't have to deal with INotifyPropertyChanged.

The Commuter View Model

Monday, May 11th, 2009

I'm working on an application to automatically manage an iTunes playlist. I've done the proof-of-concept and created a historic model. Now I want to describe the functionality of the user interface. I'll do this by drawing a UI mockup, and by defining an interface for the View Model.

The View Model is the name typically used in WPF for Martin Fowler's Presentation Model pattern. It is a class designed specifically for a view. It presents all of the information visible on the view, it accepts all of the input that the view gathers, and it provides all of the commands that the view exposes.

I like to think of a UI as merely a convenience. If the user had a compiler instead of a UI, he could still use your application. The View Model is the user's API to your program.

The View Model
An API is best defined as an interface. Here's the API that we are presenting to the user:

public interface IPlaylist
{
    string Name { get; }
}

public interface IPodcast
{
    string Name { get; }
    bool Selected { get; set; }
    int Rank { get; set; }
}

public interface IPodcastEpisode
{
    string Name { get; }
    string PodcastName { get; }
    string DurationAsString { get; }
}

public interface ICommuterViewModel
{
    IEnumerable<IPlaylist> Playlists { get; }
    IPlaylist SelectedPlaylist { get; set; }

    IEnumerable<IPodcast> Podcasts { get; }

    IEnumerable<IPodcastEpisode> Queue { get; }
    IPodcastEpisode SelectedEpisode { get; set; }
    string TotalDurationAsString { get; }

    ICommand Skip { get; }
}

This API lets the user see all of their playlists, and select one. It lets them see all of their podcasts, and individually select and rank them. Then it shows their queue, lets them select an episode, and skip it.

The mockup
For the user's convenience, we will also provide this view (my code is much neater than my drawing):

Commuter Mockup

You can see how each of the View Model properties maps to the view. With this interface, mockup, and Blend, a designer can create a nice convenient UI for our user. Just so they don't have to get their hands dirty with a compiler. That's our next step.

Modeling an iTunes automation application

Saturday, May 9th, 2009

I listen to podcasts on the way to work. I've created a playlist called "Commute" that I use to make this hands-free. Here's my workflow:

  • Dock the iPod before bed, which launches iTunes and updates play counts.
  • Delete all episodes from the Commute playlist with a play count of 1.
  • iTunes (usually) downloads the latest episodes overnight. If not, I kick it in the morning.
  • Drag new episodes into the Commute playlist.

In the car, I just start the playlist and it takes me to work and back.

It has occurred to me on more than one occasion that this process could be automated. I finally decided to take on the task. After a quick POC with the iTunes COM interface, I decided that it was possible.

Commuter
The application, currently named "Commuter", will automatically queue selected podcasts in a specified playlist. Here's what a user can do:

  • See playlists and podcast subscriptions managed in iTunes.
  • Select a playlist into which to queue the episodes.
  • Select podcast subscriptions to use.
  • Rank subscriptions to influence the order in which they are queued.

From this information, the program manages the queue within the selected playlist.

The historic model
I prefer to use an analysis technique that I call Historic Modeling. This technique captures more information than data modeling alone. It includes all of the changes that can be made to the system, and the relationships among those changes. Here is the model I came up with:

commuter

Each bubble represents a fact. A fact is an entity that was created, a property that was changed, or an operation that was performed. The arrows between facts indicate that one has to occur before the other. The successor fact has knowledge of its predecessor.

A Commuter, for example, has knowledge of a MusicLibrary, and cannot be created before the MusicLibrary exists. A SelectedPlaylist records the fact that a Commuter has selected a Playlist. It also records the Commuter's prior selection, if any.

Facts are immutable. A Commuter can select a new Playlist, but the fact that he had once selected a particular Playlist will always be true.

Some of these facts are operations that the user performs, some of them represent iTunes synchronization, and some represent decisions of the program itself. All of these facts are interrelated. User actions are made with knowledge of iTunes objects, and program decisions are made because of user actions.

The next steps will be to mock up the UI and define a view model.