A Software Assembly is a collection of multiple ‘built' configuration items, some or all of which have their own development process and their own release identifiers. These complete items are incorporated together into an assembly. The assembly aspect is the key to this technique.
Two separate products in different markets being developed in parallel is not an assembly-it is two separate instances of the Monolithic Development  technique. Assembly components can be any kind of artifact. An entire application, some documents, a library of common routines. Anything that makes sense to manage separately can be a component of a software assembly, including lower-level assemblies.
The Software Assembly technique is useful when the individual members have different development teams, different technologies, or if they are at different levels of maturity. To justify this technique, at least one assembly member should be released separately from the assembly. If the members are only available as part of the assembly, it will be simpler and easier to stay with the monolithic approach.
A common scenario is a member shared between two products. If you have, say, a Mac music player and a Windows music player, and both use a single decoder library for music formats, you may have two separate assemblies with a common shared component. You might still release the two products in lock-step, using Monolithic Development. This could be the case for a Java-based music player, for example. But if they really are separate products, you probably won't.
When changes or problems are reported, it is important to correctly identify the member(s) impacted. The separate members will have separate release sequences, separate schedules, and possibly separate development teams. The initial stages of the change or problem work flow must route the ticket to the correct member team. With separate development teams, there may be separate change tracking systems. Obviously, the systems need to be integrated to enable passing tickets across the border. If the members are managed with the same system, some sort of virtual repository must be implemented, using separate databases or identification fields within a single database.
In addition to the "major" assembly members, which have external visibility and a separate release schedule, there may be "minor" members. One obvious example is a lightweight member for the actual assembly itself. This would likely include documentation, any build or installation scripts or programs, and other "global" artifacts. There is no need for a separate release schedule for this member-it will use the release identification and schedule of the assembly.
When an assembly includes many major members, they may well share common libraries. These shared libraries are very likely to need their own release identification, unless the library is tightly coupled to one of the components. As an example, a
library for reading a file format might be tightly coupled to the application that uses that file format. But a library of common string functions would not be tightly coupled with any major member, although it might change with any of them. As a result, the string
library would need its own release schedule, while the file format library might be able to use the same release schedule as its parent application.
A Software Assembly is predicated on having multiple, separately-managed assembly members. This leads immediately to several advantages. Having separate members
allows separate development cycles for the members. It also means flexible release schedules. So long as the dependencies are met, an assembly can be released without having to wait for every single member to be updated. The assembly/member hierarchy means that divided teams can be managed more flexibly, and that different technologies (target platform, implementation language, etc.) can be included. Finally, partitioning the release into members formalizes the allocation and hand-off of problems and resources.
Unfortunately, every advantage can be a disadvantage as well. The top-level "assembly"
configuration item, as well as any interdependent components, must track the development and versioning of the separate members. The versioning of high-level members either must advance every single time a low-level member advances, or must skip over intermediate releases of low-level members. In the latter case there are
significant compatibility risks.
Dividing the assembly into separately managed members creates control issues. The formal hand-off of resources or problems usually means giving up control as well.
Suddenly, bug-fix priority has to be negotiated instead of assigned. A project team responsible for a shared component will be adjusting the release schedule of that component to support all of its different clients. This can put the assembly's master schedule at risk, if a different assembly is demanding significant work in a shared member.
The ultimate risk springs from shared dependencies. If two members of an assembly, or members of two separate assemblies, depend on a single lower-level member, there is
the real risk that the two dependencies will get out of sync. If member A requires library version 3.1, while member B still uses (and ships with) library version 2.3, a conflict may well occur at build, assembly, or (worse) installation time. This problem is known as "DLL
Hell" in the Windows world, and caused significant problems with early releases of the .NET platform. Now, operating systems have a mechanism for working around this issue with shared libraries. Your assembly will have to make whatever changes are needed to address this, and your testing and release teams, plus of course technical support, will have to know how to make things work.
Implementing Software Assembly requires separating your change management repository into separate repositories, or implementing discriminating fields to emulate the
separation. Almost immediately, releases and schedules will begin to diverge. You will likely have to restructure the code in your source repository to allow independent members to be build in isolation. The dependencies between members are a vulnerability that nearly everyone overlooks, usually at the expense of a day or so of lost productivity. The right answer is usually to formalize checking the provided release of each member as part of the build or install script. (I remember one instance where a blown install left us with C++ header files that were not in sync with the underlying libraries. It's amazing how much productivity you can lose by trusting the system files on a young operating system.)
The costs of adopting Software Assembly are generally straightforward, and higher than you will probably like. Formalizing and segregating the components will have costs at nearly all levels. Project managers will have to take the time to separate issue tracking and scheduling, development managers will have to accommodate separate development cycles, including independent testing. Developers will frequently have to write additional code to formally separate the members-there is a big difference between a collection of common functions and a truly independent library.
The CM team will have to implement whatever code repository changes are needed, as well as restructuring the change-control repository. The build and release folks will have to provide a separate build script for the emancipated members, as well as possibly adding separate build invocations. And of course the "separate" member will have to be integrated back into the client assemblies, requiring changes to the build and installation scripts of the client(s). While most of those costs will be up-front, the build and release processing will be ongoing costs. Depending on the number of members to be split out, and their size and complexity, you may need additional staff to perform these duties.
 See Software Configuration Management: Getting Started for a discussion of the Monolithic Development technique.