How to delete a Correspondence object

A Correspondence object records the fact that an atomic operation has taken place. No matter what happens in the future, the fact remains that that operation occurred. No action can truly expunge an object.

But one atomic operation can negate the effect of another. It can leave the system as a whole in a state equivalent to one in which the first operation never happened. At least from an application point-of-view.

So the way to delete a Correspondence object is to create a successor that negates it.

The exists method
The CorrespondenceObject base class defines a method called exists. A derived class overrides this method to return true if the object exists, or false if it does not. This test must be performed by examining the results of queries. So the method returns true if the query for negating objects returns an empty set.

Suppose we want the ability to delete an issue in our bug tracking system. To do so, we have to define a new class called IssueDelete that causes the issue not to exist. We'll start with this simple definition.

@Correspondence
public class IssueDelete extends CorrespondenceObject {

	// Issue that was deleted.
	private PredecessorObj<Issue> issue;

	public IssueDelete(Issue issue) {
		this.issue = new PredecessorObj<Issue>(this, "issue_delete", issue);
	}

	public IssueDelete(Memento memento) throws CorrespondenceException {
		this.issue = new PredecessorObj<Issue>(this, "issue_delete", memento);
	}
}

The Issue class is modified to query for IssueDelete successors and to cease to exist when one is found.

@Correspondence
public class Issue extends CorrespondenceObject {

	// The project to which this issue belogs.
	private PredecessorObj<Project> project;

	// Query for deletion of this issue.
	private Query<IssueDelete> delete = new Query<IssueDelete>(this)
		.joinSuccessors("issue_delete");

	// Identity.
	private @Field UUID id;

	public Issue(Project project) {
		this.project = new PredecessorObj<Project>(this, "project_issue", project);
		this.id = UUID.randomUUID();
	}

	public Issue(Memento memento) throws CorrespondenceException {
		this.project = new PredecessorObj<Project>(this, "project_issue", memento);
	}

	public Project getProject() {
		return project.getObject();
	}

	@Override
	protected boolean exists() {
		return delete.isEmpty();
	}

	public void delete() {
		getCommunity().addObject(new IssueDelete(this));
	}
}

The delete query looks for successors in the "issue_delete" role of the type IssueDelete. If any such objects are created, the results of this query will no longer be empty. That will cause the exists method to return false. Just such an object is created in the delete method. Notice that the object is added to the Community, which is the repository for all Correspondence objects. We'll have a lot more to say about the Community in later posts.

Restore a deleted object
But this feature is not finished. While the IssueDelete class makes it possible to delete an issue, that deletion cannot be undone. Just like any other atomic operation, the fact that the deletion occurred cannot be expunged. In order to undo the deletion, we need to delete IssueDelete.

We do this by creating a class called IssueRestore.

@Correspondence
public class IssueRestore extends CorrespondenceObject {

	// The deletion to undo.
	private PredecessorObj<IssueDelete> delete;

	public IssueRestore(IssueDelete delete) {
		this.delete = new PredecessorObj<IssueDelete>(this, "undo", delete);
	}

	public IssueRestore(Memento memento) throws CorrespondenceException {
		this.delete = new PredecessorObj<IssueDelete>(this, "undo", memento);
	}
}

And then we modify IssueDelete to exist only when there are no such successors:

@Correspondence
public class IssueDelete extends CorrespondenceObject {

	// Issue that was deleted.
	private PredecessorObj<Issue> issue;

	// Query for restore.
	private Query<IssueRestore> restore = new Query<IssueRestore>(this)
		.joinSuccessors("undo");

	public IssueDelete(Issue issue) {
		this.issue = new PredecessorObj<Issue>(this, "issue_delete", issue);
	}

	public IssueDelete(Memento memento) throws CorrespondenceException {
		this.issue = new PredecessorObj<Issue>(this, "issue_delete", memento);
	}

	@Override
	protected boolean exists() {
		return restore.isEmpty();
	}
}

Uniqueness of a deletion
We are almost done. There's just one last problem. Delete an issue, and it disappears. Restore it, and it comes back. But if we delete it a second time, nothing happens. Why?

The set of an object's predecessors and fields is its identity. If you create an instance of the same class with the same predecessors and fields, you will get the same object. The second time we attempt to delete an issue, we only succeed in reasserting the existence of the first deletion. For this system to work, deletions must be unique.

To make an object unique, we give it a unique field. We add a UUID to IssueDelete:

@Correspondence
public class IssueDelete extends CorrespondenceObject {

	// Issue that was deleted.
	private PredecessorObj<Issue> issue;

	// Query for restore.
	private Query<IssueRestore> restore = new Query<IssueRestore>(this)
		.joinSuccessors("undo");

	// Identity.
	private @Field UUID id;

	public IssueDelete(Issue issue) {
		this.issue = new PredecessorObj<Issue>(this, "issue_delete", issue);
		id = UUID.randomUUID();
	}

	public IssueDelete(Memento memento) throws CorrespondenceException {
		this.issue = new PredecessorObj<Issue>(this, "issue_delete", memento);
	}

	@Override
	protected boolean exists() {
		return restore.isEmpty();
	}
}

And so with this pattern a Correspondence object can be deleted, restored, and deleted again. Deletion and restoration both represent atomic operations, and are therefore both recorded as new objects. Although they represent modifications of the object graph from an application point-of-view, these Correspondence objects adhere to the law that an object is immutable. As we've discussed in previous posts, this is the law that makes synchronization possible.

Leave a Reply

You must be logged in to post a comment.