TFSOM

Topics: Developer Forum
Coordinator
Jun 28, 2006 at 6:37 PM
Since I have some experience with the Team Foundation Server Object Model (TFSOM), I thought of sharing a sample I did for my company that I guess does some of the job we are trying to achieve here.
This program queryies a Project ($\ProjectName\) for all of it´s labels.

I know it can be improved but it´s a start I guess.

The Code so far:
---------------------------------------
/// <summary>
/// Method that connects to a Team Foundation Server.
/// </summary>
/// <param name="url">Url of the Team Foundation Server</param>
/// <returns>Returns a TeamFoundationServer Object.</returns>
public static TeamFoundationServer Connect(string url)
{
TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(url);

if (tfs != null)
return tfs;
else
throw new Exceptions.TeamFoundationServerNotFoundException(url);
}
Coordinator
Jun 28, 2006 at 6:38 PM
/// <summary>
/// Method that gets the Version Control Server Service out of a Team
/// Foundation Server Object.
/// </summary>
/// <param name="teamFoundationServer">Team Foundation Server Object.</param>
/// <returns>Returns a Version Control Server object.</returns>
public static VersionControlServer GetVersionControl(TeamFoundationServer teamFoundationServer)
{
//Check the instance of the team foundation server variable.
if (teamFoundationServer == null)
throw new Exceptions.TeamFoundationServerNotFoundException("The team foundation server parameter cannot be null!");

VersionControlServer vcs = (VersionControlServer)teamFoundationServer.GetService(typeof(VersionControlServer));

if (vcs != null)
return vcs;
else
throw new Exceptions.SourceControlServerNotFoundException();
}
Coordinator
Jun 28, 2006 at 6:39 PM
/// <summary>
/// Gets a Team Project by it´s qualified name (e.g.: "$\ProjectName\").
/// </summary>
/// <param name="ProjectName">Qualified Project Name.</param>
/// <param name="versionControlServer">Version Control Server object.</param>
/// <returns>Returns a team project by it´s qualified name.</returns>
public static TeamProject GetProject(string ProjectName, VersionControlServer versionControlServer)
{
//Check the instance of the version control server variable.
if (versionControlServer == null)
throw new Exceptions.SourceControlServerNotFoundException("The version control server variable cannot be null! Try using the GetVersionControl method!");

TeamProject tp = versionControlServer.TryGetTeamProject(ProjectName);

if (tp != null)
return tp;
else
throw new Exceptions.TeamProjectNotFoundException(ProjectName);
}

/// <summary>
/// Gets the working folder for the desired project.
/// </summary>
/// <param name="ProjectName">Qualified Project Name.</param>
/// <param name="versionControlServer">Version Control Server object.</param>
/// <returns>Returns the Working folder object.</returns>
public static WorkingFolder GetWorkingFolder(string ProjectName, VersionControlServer versionControlServer)
{
Workspace[] ws = versionControlServer.QueryWorkspaces(null, null, Environment.MachineName);

return ws0.GetWorkingFolderForServerItem(ProjectName);
}
Coordinator
Jun 28, 2006 at 6:39 PM
/// <summary>
/// Gets an array of Labels of the Project in the project working folder indicated.
/// </summary>
/// <param name="projectWorkingFolder">Desired Working Folder.</param>
/// <param name="versionControlServer">Version Control Server object.</param>
/// <returns>Returns an array of Labels.</returns>
public static VersionControlLabel[] GetLabels(WorkingFolder projectWorkingFolder, VersionControlServer versionControlServer)
{
ArrayList al = new ArrayList();
ItemSet files = versionControlServer.GetItems(projectWorkingFolder.ServerItem, VersionSpec.Latest, RecursionType.None, DeletedState.NonDeleted, ItemType.Folder);
foreach (Item i in files.Items)
{
al.AddRange(versionControlServer.QueryLabels(null, null, null, false,i.ServerItem, VersionSpec.Latest));
}

return (VersionControlLabel[])al.ToArray(typeof(VersionControlLabel));
}
Developer
Jun 28, 2006 at 8:19 PM
Nice code samples. I don't think I ever had the patience to break it out to individual methods. I'm looking at all of this to see if there's a good way to nMock it to abstract the server away for testing.
Coordinator
Jun 28, 2006 at 8:25 PM
Thanks. I guess that´s a good starting point, since the connection to the foundation server is the first thing to do hehehe...

Anyway, about code structure and naming I guess that´s pretty much it right?

Bernardo Heynemann
Developer
Jun 29, 2006 at 1:21 AM
What I like to do for Mocking is to create an interface (IVersionControl) that gets implemented by 2 classes. The first class is a VersionControlAdapter which gives us a layer of abstraction between our code and the TFS API. The second class is a mock (VersionControlMcokAdapter) that gives us back "known" values that we can unit test against. My unit tests do not go against real data and can be run even in an environment where a data store/TFVC store is not available.

I generally have an additional layer of tests called "Acceptance Tests". These tests are generally tests that the Dev team write to test at a macro level. These generally go against a data store, or in this case a known TFVC Team Project. I try to get a copy of QA's test plan and write the acceptance tests against them, plus add my own scenarios. This way, I know that when I'm done an iteration and all of my tests pass I won't have to go back and fix too many "critical" (show-stopper) bugs that hold up QA. They happily test our just completed iteration code and I get to code away unimpeded through the next iteration.

Does this sound like something that would be helpful in our case?

-Steve
Coordinator
Jun 29, 2006 at 1:41 AM
This sounds very helpful to me.
Coordinator
Jun 29, 2006 at 2:43 AM
I find mocking a veryyyyy powerful tool.
But in the case of the Adapter class I find it very hard to do, since how can I return a TeamFoundationServer object without a Team Foundation Server? I mean, I could build a wrapper around the TeamFoundationServer object and then work with that instead, but that practice would make me have to build object wrappers around all the TFSOM objects and it's wayyyy too costly to do that and maintain that OM. Since we have codeplex up and running all the time I don't see why we shouldn't always test against the live Team Foundation Server that Microsoft has given to us :)

Steve this isn't by any means criticism! :) I think your way of mocking is REALLLY interesting and my mind is already bubbling with ideas to use that approach in a miriad of different cases, but I don't think TFSOM is the case.

If you have any ideas, please state it here and we can discuss! Man, I love to discuss with clever people :)

Bernardo Heynemann
Coordinator
Jun 29, 2006 at 2:44 AM
Why is a project working folder used? Or is that just one of the signatures for this method. This leads to the requirements that I need to drill deeper into for calrification and is on my agenda for tonight.

Fine we are showing a version tree... but what is the user interaction to get that? What are selecting from to ask for a version tree- working folder, source repository file in a branch, etc.
Developer
Jun 29, 2006 at 3:13 AM
Mocking - Yes, I can see mocking away the entire logic layer for testing at a higher level, then we'll just have to test against "known good" values - maybe lock an entire subtree of our source to use as our test data.

I've been thinking about the interface between our data provider and the various UI's... what kind of interface do we want? I was initially thinking a strongly typed data set with some sort of lazy-load behind it - we get the core info and expand it behind the scenes.

The interface into our data library should probably support both source control path ($/Project/path/path/file.ext) and physical path (c:\Views\Project\path\path\file.ext).

Thoughts? Anybody else interested in taking the first pass at a data set definition? we need to dig into the source control API and database model and pick out the object graph that makes sense for what we're going to use - likely a tree starting at the versioned item. If nobody else gets to it by Friday afternoon, I may dig into it at the close of business.
Developer
Jun 29, 2006 at 3:50 AM
Bernardo, no offense taken, I love a good difference of opinion, especially when I'm right. :)

You are right, we have this whole API spread before us to use. We can always lock down the "test source" folders once we have them structured so that we have a known and stable set of test data.

My assertion of using an Adapter pattern is so that when changes are made in SP1, SP2, v2 or vNext, we don't have to stop supporting v.Old. We can just create a new Adapter for a newer TFS API and use a Template pattern to select the one that we want at runtime.

Also, to clarify the interface (IVersionControl) we don't have to mimic the existing TFS API, but rather would use it as a Facade to take the very granular API calls and aggregate them into higher level methods.

For right now, a simple approach is to test against CodePlex TFS, just thought I'd throw out something that we can all chew on.

Thoughts? Comments? Questions? Concerns? Gripes? Flames? :-o

-Steve
Coordinator
Jun 29, 2006 at 5:22 AM
I agree on most parts but I don't think we can take 1 class that implements IVersionControl and have all the functions we want to call within it without creating something so monlithic that it would be a nightmare. In addition, saying that IVersionControl can reprsent all potential needs for now and in the future would also be ill advised because once we release the interface it should not change. No more and no less.

Once you have released an interface it must always stay that way or it will break older code that uses that interface. An abstract class with atleast one concrete class that inherits from it is much preferred. You can add methods to a class and not break older code that uses that class.

Interfaces should be used to achieve similar affect as multiple inheritance and for value types that are ploymorphic, or when it's truly and interface that will not change.


I have no doubt we will write an adapter layer in a dll named CodePlex.Vertigo.APIAdapter versus an adapter class (in fact I have begun). One class will not be sufficient or maintainable.

ditto on responses etc....
Developer
Jun 29, 2006 at 5:28 AM
I/We will need to translate this into the wiki somewhere, probably with a real diagram, but what I'm seeing from this discussion is this psuedocode:


void HandleUserRequest( object sender, EArgs bleh)
{
ITFSVersionProvider provider = TFSVersionProviderFactory.GetCurrentProvider();

VertigoSourceTreeDataSet ds = new VertigoSourceTreeDataSet( );

// Don't know which side of the line should manage the asynch on this one
provider.BeginLoad( ds, (string) EArgs.FileInfo.Path, new this.LoadCompleteHandler );
}

void LoadCompleteHandler( object sender, EArgs whatever )
{
// Display stuff
// Kick off Lazy loaded elements
// Whatever
}

Then, down inside the TFSVersionProvider we'd have a set of helper objects wrapping the core TFS API, but these would initially be a fairly thin layer providing proxied access to the API and some limited management of the connection objects.

That match with what you're saying/thinking, Steve?
Developer
Jun 29, 2006 at 6:26 AM
- Following up to Michael's post -
I do agree that we won't have just a single interface, in reality we'll have a small group of tightly related interfaces. Right now, I don't thenk we need to worry too much about getting the specific interface "right" since there will only initially be one client and two implementations (UserControl, TFS API, Mock Testing API). The interfaces need to be there more for dependency injection purposes rather than behavioral contracts.

In the same way, if we return a strongly typed data set then we can add tables and evolve the details of the schema as we go without having to worry about how it effects users of other portions of the data set. Only the code that looks at the labels table would have to change.

I think if we assume we're not going to get it right the first time and isolate the change from spanning the system we'll end up with a much better product.
Developer
Jun 29, 2006 at 6:28 AM
Hey! "(in fact I have begun). " No fair - you're not allowed to sneak ahead of us!

...

...

Course, I shouldn't talk about that "practice Addin" I put together to make sure I understood the basic command and tool window behaviors of visual studio addins blush :0
Coordinator
Jun 29, 2006 at 8:23 AM
I must admit--- strongly typed dataset does not make me happy. For multiple reasons.

  1. We don't get the data from TFS in a datatable and dataset style. We get them as entities. The translation will be costly.
  2. I'm an entity person to begin with I like to move objects around that contain intelligence about themselves in terms of rules.
  3. The schema of a complicated stream would itself be very complicated - why should we reinvent portions the TFS Warehouse?
  4. It just makes my gut hurt.

There's more but it's getting late or really early for the next business day.

Developer
Jun 29, 2006 at 11:16 AM
I'm happy as long as we have a layer that we control between our code and the TFS API.

On the typed-dataset/entity debate, I'm moreinclined to go with an entity solution. We can bind against them as needed in a UI, but they can encapsulate sufficient logic to be "self-aware". This should lower the overall code needed to implement the system while also containing the blast area of any code changes that will need to be made in the future.

I propose a general addition to the Vision statement to the effect that "an overarching design goal will be the maintainability of the system."

I've been responsible for maintenance on a number of systems in the past and know how "the next poor bastard" feels when the original developer wrote some "neat" or "tricky" code without leaving an explanation. I have made it a general practice to not do that to others. My mantra: "Simple = Good".

-Steve
Coordinator
Jun 29, 2006 at 12:17 PM
Simple equals good. Bravo! I knew I liked you.

:-)