Apr 12 2005

Which encoding is better for webservices?

Category: .architectureAmit Bahree @ 6:25 pm

There are two options when it comes to encoding in the context of webservices, e.g. .NET and Weblogic use document literal encoding while IBM and other vendors (Java) use RPC encoding. What is the difference and which one is better for which scenarios? Also, how easy is it to switch between the two?

Well for those new to webservices, there are two options that you can choose when encoding your wsdl messages.

So, how do they look like. If I “borrow” and example from Sun, if below was your original class in Java:


package com.examples.xmlstring;
import java.rmi.Remote;

import java.rmi.RemoteException;

public interface IStringService extends Remote {

    public String sayXMLHello(String xml)
        throws RemoteException;
    }

The rpc encoding for that looks like this:

<binding name="IStringServiceBinding" type="tns:IStringService">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="rpc"/>
    <operation name="sayXMLHello">
        <soap:operation soapaction=""/>
            <input>
               
<soap:body encodingstyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="http://www.examples.com/wsdl/StringService"/>
            </input>
            <output>
               
<soap:body encodingstyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="http://www.examples.com/wsdl/StringService"/>
            </output>
    </operation>
</binding>

And the document encoding looks like:

<?xml version="1.0" encoding="UTF-8"?>
    <env:envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns0="http://www.examples.com/types">
        <env:body>
            <ns0:sayxmlhello>
                <string_1>Hello World</string_1>

            </ns0:sayxmlhello>

        </env:body>
</env:envelope>

I think in the end the real thing to keep in perspective is who and what will be consuming this? If your clients are primarily MS-based (COM/.NET) then you are better off with literal encoding, on the other hand if its primarily J2EE client then you are better off with the other one. But, more interestingly if you don’t know (or in other words can be both), then which road to take?

Share

Tags:


Mar 24 2005

Web Services are not distributed objects

Category: .architectureAmit Bahree @ 4:20 am

Werner Vogel, CTO of Amazon.com has an article which was published a few months ago where we talks about the misconception of how most people think that web services are distributed objects. Here is an excerpt from the article.

The hype surrounding Web services has generated many common misconceptions about the fundamentals of this emerging technology.

Web services are frequently described as the latest incarnation of distributed object technology. This misconception, perpetuated by people from both industry and academia, seriously limits broader acceptance of the true Web services architecture. Although the architects of many distributed and Internet systems have been vocal about the differences between Web services and distributed objects, dispelling the myth that they are closely related appears difficult.

Many believe that Web services is a distributed systems technology that relies on some form of distributed object technology. Unfortunately, this is not the only common misconception about Web services. In this article, I seek to clarify several widely held beliefs about the technology that are partially or completely wrong.

Fundamental Errors: At the International World Wide Web Conference in May 2003, a smart and gifted Internet architect I will call Peter asked me, “Don’t you think Web services will fail like all the other wide-area distributed object technologies that people have tried to build?” I was baffled. How could someone like Peter still view Web services as distributed object technology? Yet, he is not alone in his stubbornness: many developers, architects, managers, and academics still see Web services as the next episode in a saga that includes Corba, DCOM, and remote method invocation (RMI). Web services are distributed systems technologies, but that is where the common ground ends. The only possible relation is that Web services are now sometimes deployed in areas where distributed object applications have failed in the past. Within the distributed technology world, it is probably more appropriate to associate Web services with messaging technologies because they share a common architectural view, although they address different application types.

Given that Web services are based on XML documents and document exchange, we could say their technological underpinning is document-oriented computing. However, exchanging documents is very different from requesting an object’s instantiation, requesting a method’s invocation on the basis of the specific object instance, receiving that invocation’s result in a response, and releasing the object instance after several such exchanges.

I frequently encounter about a dozen other statements that fall into the same basic category. I hear people say, for example, that “Web services are just remote procedure calls for the Internet,” or “You need HTTP to make Web services work.” Before addressing several of the more common misconceptions, we should define a Web service in its purest form in order to begin with a clear model.

Share

Tags:


Mar 01 2005

Examination of Data Structures in .NET 2.0

Category: .architectureAmit Bahree @ 5:01 pm

You might have seen this already, if not, MSDN has a six-part article extensively examining the data structures in .NET 2.0 covering the usual suspects and then some not-so-usual unless you have written some compilers such as BST’s, graphs, red-back trees, etc. Quite an interesting read when you have some time.

Share

Tags:


Jan 29 2005

Enterprise Library

Category: .architectureAmit Bahree @ 2:39 pm

Microsoft released the Enterprise Library based on ACA.NET which our engineering guys here at Avanade, work their butts off! Enterprise Library features new and updated versions of application blocks that were previously available as stand-alone application blocks. All Enterprise Library application blocks have been updated with a particular focus on consistency, extensibility, ease of use, and integration.

The application blocks that comprise the Enterprise Library are the following:

  • Caching Application Block. This application block allows developers to incorporate a local cache in their applications.
  • Configuration Application Block. This application block allows applications to read and write configuration information.
  • Data Access Application Block. This application block allows developers to incorporate standard database functionality in their applications.
  • Cryptography Application Block. This application block allows developers to include encryption and hashing functionality in their applications.
  • Exception Handling Application Block. This application block allows developers and policy makers to create a consistent strategy for processing exceptions that occur throughout the architectural layers of enterprise applications.
  • Logging and Instrumentation Application Block. This application block allows developers to incorporate standard logging and instrumentation functionality in their applications.
  • Security Application Block. This application block allows developers to incorporate security functionality in their applications. Applications can use the application block in a variety of situations, such as authenticating and authorising users against a database, retrieving role and profile information, and caching user profile information.

Different applications have different requirements and you will not find that every application block is useful in every application that you build. Before using an application block, you should have a good understanding of your application requirements and of the scenarios that the application block is designed to address.

You can also get some more information on Tom’s blog.

Share

Tags:


Jan 18 2005

Testing .NET Application Blocks (Version 1.0)

Category: .architectureAmit Bahree @ 5:55 pm

A little while ago while I was one of the industry advisors for the PAG group at Microsoft. One of the last things I did in that capacity was provide input to the the Testing App blocks for MS. Microsoft has finally released this and can be downloaded here.

Testing .NET Application Blocks covers many testing areas that were used during testing and verification of the various application blocks provided by Microsoft’s patterns & practices group, such as functional, globalization, performance, integration, and security. The guide uses code examples, sample test cases, and checklists to demonstrate how to plan and implement each type of test; the guide also recommends tools to use to run the tests. It covers test considerations to be taken into account when customizing these application blocks or integrating them with your own applications.

Because this guide focuses on testing NET application blocks, its scope does not include user interface (UI) testing or database testing. Although the guidance is developed within the context of Microsoft patterns & practices application blocks, it still applies to testing .NET code in general.

[Listening to: Nach Deutschland - The Bourne Supremacy - The Bourne Supremacy (02:40)]

Share

Tags:


Nov 27 2004

Data Concurrency and Mobile Applications

Category: .architectureAmit Bahree @ 1:31 am

As I start working on some stuff, I have been thinking of this. Essentially when you are dealing with mobile devices, by their very nature they are disconnected in nature, and not always on a network (such as desktop or laptop). In multi-user environments, the challenge is how to you keep the data concurrent between the “online” and “offline” version? Typically there are two scenarios either you do optimistic concurrency or pessimistic concurrency. The earlier being a device (rather a client/user), submits both the currents and previous state and then the data is updated only if there have not been any changes since the device last synched. Pessimistic locking is when the application locks all the records the device intents to update – this of course is not a good idea.

But, in real life, neither of these issues are really ideal. How best to deal with situations where e.g. a sales person who is on the road could not (or does not) connect to the network to synch up and the person in the office updates the record? How do you deal with those situations. One solution is to update only the changes fields, and the rest are either rejected or the user needs to manually confirm each change and pick which ones take precedence. This along with the fact that in real life data can be segregated to some extent on a user level would suffice? Another thing to dwell upon, for most Mobile applications (especially disconnected), going the SOA way is what makes more sense. Since each time you sync up or submit something (if its on gprs for example), your client running on the device should just consume the service and really cannot be bothered by any more details than that (more on this on another post soon).

But, still seems quite fragile with lots of areas for failure. What are you thoughts? Is there a better way to do this that would make it more robust?

Share

Tags:


Oct 28 2004

HTTP Modules and HTTP Handlers

Category: .architectureAmit Bahree @ 4:27 pm

ASP.NET has a pretty interesting HTTP runtime architecture if you have ever dug into the covers. The basic run-time support has API’s as powerful as ISAPI (in IIS). ASP.NET offers IHttpHandler and IHttpModule interfaces that offer you similar functionality. I will get into each one in a little bit of detail, but at a high level IHttpHandler is analogous to what would be an ISAPI extension in IIS and IHttpModule would be analogous to what a ISAPI filter in IIS. As a matter of fact, each asp.net page (.aspx) you have in your project is essentially an HTTP Handler.

HTTP Handlers: The “handlers” are nothing but assemblies (class libraries) that implement IHttpHandler and IHttpAsyncHandler interfaces. Essentially, ASP.NET maps each http request to a http handler. The handler in turn enables processing individual url’s or groups of url’s with similar extensions such as .aspx, .asmx, .ascx, .disco, etc. The handler can be synchronous or asynchronous, the earlier does not return until it finished processing and the latter typically launches a separate process.

When you inherit from IHttpHandler handler, you need to implement both the ProcessRequest method (which processes the individual http requests) and the IsReusable property (which specifies if pooling is supported or not). If you have a more complex set of logic then you should look at inheriting IHttpHandlerFactory, as that allows finer control and you can create different handler based on your need. E.g. you can create a separate handler for GET and PUT.

When you are finished building the handler (essentially compiling your assembly), you need to register it to use it. To register a handler essentially you copy the assembly in the bin folder of where you web app is running and create a section in the web.config. (I’ll have a sample in a bit in this post). You might also want to ensure that the HTTP handler’s extension is registered with IIS.

General process of Building a Handler:

  1. Implement the IHttpHandler interface
  2. Handle the ProcessRequest method and IsRuseable property.
  3. Deploy the assembly
  4. Register the handler

Below is an example of a handler that processes request for the files with the extension “.desigeek”. Note, that you do not need to have a physical file present with that extension.

public class DesigeekHandler : IHttpHandler
{
   
bool IHttpHandler
.IsReusable
    {
       
get
       
{
           
return false
;
       
}
    }

void IHttpHandler.ProcessRequest( HttpContext context )
{
   
HttpResponse
response = context.Response;
    
    response.Write( “< html>” );
    response.Write(
“< body>”
);
    response.Write(
“< h1> Hello from Desigeek custom handler. < /h1>”
);
    response.Write(
“< /body>”
);
    response.Write(
“< /html>”
);
}

Register the HTTP handler by creating an entry in the web.config:

<httpHandlers>
    <add verb=* path=*.desigeek type=MyHTTPHandler.DesigeekHandler, MyHTTPHandler
/>
httpHandlers>

Below is a sample of what the request would look like. Notice the URL requested is “foo.desigeek” (and there is no physical file with that name on the file system. The first screen-shot is of the web application (which basically is one label which is updated with the system time on PageLoad). The second one is where the handler kicks in.

HTTP Module: HTTP modules are classes that can be configured to run in response to events that fire during the request for an ASP.NET resource (which can be serviced by any handler). An HTTP module is an assembly that implements the IHttpModule interface and handles events. ASP.NET ships with a number of modules out of the box e.g. SessionStateModule is used to supply session state services to an application.

If you check your machine.config you will there are a number of handlers that come with ASP.NET. e.g you might look at something like:
< httpModules>
    < add name=”OutputCache” type=”System.Web.Caching.OutputCacheModule, …/>
    < add name=”Session” type=”System.Web.SessionState.SessionStateModule, …/>
    < add name=”WindowsAuthentication” type=”System.Web.Security.WindowsAuthenticationModule, …/>
    < add name=”FormsAuthentication” type=”System.Web.Security.FormsAuthenticationModule …/>
    < add name=”PassportAuthentication” type=”System.Web.Security.PassportAuthenticationModule …/>
    < add name=”UrlAuthorization” type=”System.Web.Security.UrlAuthorizationModule, …/>
    < add name=”FileAuthorization” type=”System.Web.Security.FileAuthorizationModule, …/>
< /httpModules>

Basically you follow the similar process as HTTP handlers. Once you have created your assembly you deploy it to the bin folder and register your handler. The general process for writing an HTTP module is:

  • Implement the IHttpModule interface.
  • Handle the Init method and register for the events you need.
  • Handle the events.
  • Optionally implement the Dispose method if you have to do cleanup.
  • Register the module in Web.config.

Here is an example which hooks into the BeginRequest and EndRequest events of HttpApplication and adds “DesigeekModule: Begin of Request“ and “DesigeekModule: End of Request“ to the response being send out to the client. The Init() function is where your register the hooks you want.

public class DesigeekModule : IHttpModule
{
   
//you register the events you are want to hook in this function
   
void IHttpModule.Init( HttpApplication context )
    {
        context.BeginRequest += ( new EventHandler( this.App_BeginRequest ) );
        context.EndRequest += ( new EventHandler( this.App_EndRequest ) );
    }

    private void App_EndRequest( object source, EventArgs e )
    {
        HttpApplication app = (HttpApplication)source;
        HttpContext context = app.Context;
        context.Response.Write( “DesigeekModule: END of Request” );
    }

    private void App_BeginRequest( object source, EventArgs e )
    {
        HttpApplication app = (HttpApplication)source;
        HttpContext context = app.Context;
        context.Response.Write( “DesigeekModule: BEGIN of Request” );
    }

    public string ModuleName {
        get {
            return “DesigeekModule”;
        }
    }
}

You register the HTTP Module by adding the following section in your web.config.

< httpModules>
   
< add name=DesigeekModule type=MyHTTPModule.DesigeekModule, MyHTTPHandler/>
< /httpModules>

When you run this (see the screen shots below), you will notice that when I goto the default.aspx I see the label with the date-time and the BEGIN and END strings. Another interesting point, if I get to one of my custom handler (e.g. foo.desigeek) then as shown below I get output from both the HTTP Module and HTTP Handler, which means there are many powerful things you can get by combining these.

Overall, the image below shows graphical representation of the process of a ASP.NET HTTP pipeline. The process starts with a request arriving at IIS. If the requested resource is configured to be handled by the ASP.NET ISAPI Extension, IIS dispatches the request to the unmanaged aspnet_isapi.dll ISAPI Extension. This ISAPI Extension passes off the request to the managed ASP.NET engine. It is important to note that during the request life cycle, one or more HTTP modules may execute, depending on what modules have been registered and what events they have subscribed to. Finally, the ASP.NET engine determines the HTTP handler that is responsible for rendering the content, invoking the handler and returning the generated content back to IIS, which returns it back to the requesting client. If you want to get creative and do some more processing, you can use the HTTP Factory. Remember, an HTTP handler factory is a class that is not directly responsible for rendering the content, but instead is responsible for selecting and returning an HTTP handler instance. This returned HTTP handler instance is then the one that is tasked with rendering the requested resource.

More Information:

Share

Tags:


Sep 21 2004

Yes, the GC *can* leak memory – there I said it!

Category: .architectureAmit Bahree @ 4:21 am

Shawn Van ness has an excellent article, that spells out how event listeners can cause memory leaks, yep even when running in managed code. Steve Main sums it up pretty well:

The main issue is the “lapsed listener” problem. This occurs when objects subscribe to events and subsequently get out of scope. The problem is that the event subscriber doesn’t get garbage collected because the event is still holding a reference to it inside of the event’s invocation list. The event subscriber is still considered reachable from the GC’s point of view. As such, it doesn’t get collected until the event goes out of scope (which is usually at application shutdown) which means that the event subscriber is effectively “leaked”.

Moral of the story: when you implement an Observer pattern, it’s important to consider the relative lifetime of events and subscribers. If implemented naively, you’ll end up having objects that live a lot longer than you think they should. Unsubscribe() is your friend.

As Fabrice writes, .NET’s delegates and events are implementations of the Observer Design Pattern . But the current problem is one more reminder that Design Patterns should not be applied blindly.

If you write the following code, you’ll see that the object instance gets correctly released and collected:

StoopidObject object = new StoopidObject();
GC.Collect();
GC.WaitForPendingFinalizers();
 
If you write the following code instead, although there is no apparent reference kept to the Observer, the Observer instance will not be released:
 
Observer observer = new Observer();
Subject subject = new Subject();
subject.SomethingHappened += new EventHandler(observer.subject_SomethingHappened);
GC.Collect();
GC.WaitForPendingFinalizers();

 

Guys from around the community came with various solutions. They call them Weak Delegates. Follow the links to learn more:

More Information:

Share

Tags:


Aug 05 2004

Exceptional Condition Handling in SQL Server 2005

Category: .architectureAmit Bahree @ 10:41 pm

This is my second part of the SQL Server 2005 posts; you can read the first part on Hosting the .NET runtime in SQL Server.

In the CLR certain conditions such as out of memory, stack overflow, etc can bring down an app domain (or process), this cannot be allowed in SQL Server 2005 when latter is acting has a host (for the CLR) as it will affect reliability and performance – couple of the key goals for SQL Server. Similarly unconditionally stopping a thread (e.g. via Thread.Abort) can potentially leave some resources in a “hung” state.

So how do we handle this? Well other hosts such as ASP.NET recycle the app domain in such situations which is OK because a web application is disconnected and not “long running”. On the other hand SQL Server can have long running batch jobs and rolling all that information back in mot cases is not an option. Our of memory conditions are pretty hard to handle even if you leave a safety buffer for memory. Since SQL Server manages its own memory and tends to use all available memory to maximise throughput this puts it in an even more difficult position. As a result of you increase the “safety net” to handle out of memory conditions you also increase the chances or getting in those situations.

The good news is that .NET 2.0 (which ships with Whidbey) handles these situations better than ver. 1.x and it notifies SQL Server about repercussions of failing each memory request. It would also facilitate running the GC more aggressively, waiting for existing procedural code to finish before starting to execute newer code and also aborting running threads if needed. In addition, at the CLR level there is also a failure escalation policy that would allow SQL Server to deal with those situations. Using this SQL Server among other things can either abort the thread causing the exception of unload the whole app domain.

If any resources fail then the CLR will unwind the whole stack and if there are any locks on them then unload the whole app domain as locks indicate there is some shared state to synchronise and most likely would not be consistent in that state. If the CLR encounters a stack overflow that is not caught by the application then it will unwind the stack and get rid of that thread.

Going back to the main goals, SQL Server will maintain transactional semantics. If an app domain is recycled then the main reason is probably due to reliability (even if at the expense of performance). As a note, all the class libraries part from the BCL that SQL Server will load have been ensured to clean up memory and other resources after a thread is aborted or an app domain is unloaded.

I will post on security and app domains at a later time.

Share

Tags:


Aug 03 2004

Hosting the .NET Runtime in SQL Server 2005

Category: .architectureAmit Bahree @ 7:13 am

I finally got around to trying out SQL Server 2005 (a.k.a. Yukon) and reading up a little on how it operates under the covers. I had earlier discussed SQL Server Express. This is my first of series of posts where I will be highlighting some of the new things a developer can do in SQL Server 2005 from a .NET perspective, since there are many DBA’s who live and breathe SQL Server I will leave all the database and T-SQL specific stuff to them.

What is a runtime host? Basically any process that loads the .NET runtime and runs code in that managed environment is a runtime host. The most common host is the Windows shell – when you double-click on a .net application, the host (i.e. the Win shell in this case), loads the runtime in memory and then loads the requested assembly. The host loads the runtime by calling a shim DLL called mscoree.dll whose only purpose in life is to load the runtime. This exposes two API’s called ICorRuntimeHost or CorBindToRuntimeEx. Till Whidbey the only parameters that can be passed to either of the API’s are:

  • Server or workstation behaviour
  • CLR Version
  • GC Behaviour
  • Enable sharing of JIT code across AppDomains

Till SQL Server 2005 there were two runtime hosts for the .NET namely ASP.NET worker process and IE each with their own priorities of the framework. When adding the CLR, to SQL Server the three main goals in ascending order were:

  1. Security
  2. Reliability
  3. Performance

Its not surprising to see Security and Reliability a higher priority than performance. You don’t want your mission critical application to run in a non-secure environment that can allow for malicious code and allow hackers to get in. Also critical applications such as databases are quite often needed to sustain the five 9′s of requirement (99.999 % uptime) especially in the Enterprise environment. Lastly performance is important (hence it is one of the goals) as you don’t want to wait forever to get your result back, but that performance gain is not at the cost of a secure of reliable system.

SQL Server is a specialised host and not a “simple bootstrap mechanism”. To ensure the SQL Server goals some changes had to be made to the CLR which are incorporated in ver 2.0 (Whidbey). To allow the “new” hosts such as SQL Server to have hooks into the CLR’s resource allocation and management the .NET 2.0 hosts can use ICLRRuntimeHost instead of ICorRuntimeHost. The host then can call the SetHostControl() method which in turn delegates to GetHostManager() for things such as thread management, etc. Here is how it would look like:


Hosting the CLR

Resource Management – SQL Server lazy loads the clr, so if you never use it then it is never loaded. Unlike the “regular” CLR hosts where the CLR itself manages the resources, SQL Server manages its own thread scheduling, synchronisation, locking and memory allocation. This is done by layering the clr’s mechanisms in top of SQL Server’s mechanisms. SQL Server uses its own memory allocation scheme, managing “real memory” rather than virtual memory. This allows it to optimise memory, balancing between data and index buffers, query caches, etc. SQL Server can do a better job if is manages all of the memory in its process.

SQL Server also uses its own thread management putting threads to sleep when not running. The is also called user mode scheduler. SQL Server internally uses cooperative thread scheduling to minimise thread context switches, as opposed to preemptive thread scheduling used by the CLR. This means that in SQL Server a thread has to voluntarily give up control as opposed to the CLR where the processes takes over control after a certain time-slice.

The hosting API’s in .NET 2.0 allow a runtime host to either control of “have a say in” resource allocation. The APIs manage units of work (called Tasks), that can be assigned to a thread (or fibre). The SQL scheduler manages blocking points, and hooks PInvoke and interop calls out of the runtime to control switching the scheduling mode. This allows SQL Server to supply a host memory allocator, to be notified of low memory conditions at the OS level and to fail memory allocations if needed. SQL Server can also use the hosting API to control I/O completion ports.

I’ll be discussing Exception Management, Code Loading and Security in this new host in later posts. Till then, if you want to learn in more details the either get the book mentioned earlier in this post or keep a lookout here. Also if you plan on installing the newly released Beta 2 then check out Bob’s post on some observations.

Share

Tags:


Jul 28 2004

Exception Management in .NET

Category: .architectureAmit Bahree @ 1:58 am

Well, this was long overdue. I had promised to upload my article that won a contest by osnews.com. I finally found a little time to upload it here, though the formatting is still a bit screwed especially on the code snippets. You can read the article. If you would like a pdf version of the same let me know and I can either put it up here or email it to you. Here is my blurb from the article:

Exceptions are a very powerful concept when used correctly. An important cornerstone in the design of a good application is the strategy you adopt for Exception Management. You must ensure that your design is extensible to be able to handle unforeseen exceptions gracefully, log them appropriately and generate metrics to allow the applications to be monitored externally.

Share

Tags:


Jul 22 2004

Class Designer in Visual Studio 2005 will not support UML

Category: .architectureAmit Bahree @ 2:16 am

As Ramesh writes in an old post, the Class Designer is not a UML tool but uses the notation. He goes on to say the prime audience for this tool is the Developer then what do the Architects use? Is there a different version? I personally am a bit cheesed off by this. I love UML and have used it extensively and know the value (and pains) it can bring, like with everything else there are pros and cons. Like I said in mu comment, what is Microsoft’s issue in supporting UML? Is it more politics since Rational was bought by IBM or is there some other “real” reason?

I work for a large Systems Integrator and we are also one of the largest global MS partners. A lot of the clients I am involved with have almost every technology/platform running somewhere on their network and a very small percentage are only Microsoft. So, if this is something proprietary which I as an Architect cannot “integrate” with other pieces of a complex enterprise application that maybe running on J2EE then why would I want to use this?

What happened to all the “No Application is an Island” push? Was it only a marketing ploy? The other question is, Rational (or now IBM) will be improving Rose/XDE to counter Microsoft’s threat to their dominance in this market, and if that does support UML then why would I not use that?

Thoughts?

Share

Tags:


« Previous PageNext Page »
Get Adobe Flash player