Skip to content

Modularizing Assemblies

Jasper Blues edited this page Aug 8, 2019 · 38 revisions

Typhoon allows you to group related components together under a TyphoonAssembly sub-class.

  • Keep things organized and group related parts of an application assembly.
  • Expose an interface containing the public component(s) to be produced. The internal details need not be exposed.
  • Allow plugging in different implementations of an assembly when [activating](Activating assemblies).

How do I make one assembly use components from another?

While grouping components together in logical, modular TyphoonAssembly units is great, they're not very useful if they are all isolated from each other.

Let's say you have separate TyphoonAssemblys for each of your application's three main concerns: UI (UIAssembly), network operations (NetworkComponents) and persistence (PersistenceComponents).

In NetworkComponents you might have a httpClient that handles RESTful API calls, like so:

@implementation NetworkComponents

- (id<SignUpClient>)httpClient
{
    return [TyphoonDefinition withClass:[HttpClient class] 
        configuration:^(TyphoonDefinition* definition)
    {
        //etc. . . 
    }];
}

@end

Now let's suppose we have a signUpViewController in UIAssembly that takes a new user's credential information and needs httpClient to submit the information onto the backend. No problem:

@interface UIAssembly : TyphoonAssembly

// Typhoon will automatically proxy the two collaborating assemblies 
// (NetworkComponents and TyphoonAssembly<PersistenceComponents>) here.
@property(nonatomic, strong, readonly) NetworkComponents* networkComponents;

// Collaborating assemblies can be backed by a protocol. We declare 
// type using TyphoonAssembly<FactoryProtocol> syntax to tell Typhoon
// that this is a collaborating assembly. In the app's own classes no further
// coupling to Typhoon is necessary, and we may declare a property as type
// id<FactoryProtocol>, avoiding your classes from being aware of Typhoon.
@property(nonatomic, strong, readonly) TyphoonAssembly<PersistenceComponents> persistenceComponents;

// Local components that require components from collaborating assemblies ... 
- (RootViewController *)rootViewController;

- (SignUpViewController *)signUpViewController;

- (StoreViewController *)storeViewController;
@end

And now to use a definition from another assembly . . .
```objective-c @implementation UIAssembly

// signUpViewController consumes httpClient component from 'NetworkComponents'

  • (SignUpViewController )signUpViewController { return [TyphoonDefinition withClass:[SignUpViewController class] configuration:^(TyphoonInitializer initializer) {
    [definition injectProperty:@selector(client) with:[_networkComponents httpClient]]; }]; }

... // Other UIAssembly components here

@end


Viola.

<br/>

###Activating

We can provide a different realization of the `NetworkComponents` or `PersistenceComponents` when [activating the assembly](Activating Assemblies) as long as the objects they build conform to the same class or protocol. This gives plenty of flexibility, for example, to patch out the network components for stubs in integration tests. 

* If you declare your assemblies by concrete type, you can provide one that overrides that type (eg TestNetworkComponents for NetworkComponents). 
* If you declare your collaborating assemblies as backed by a protocol you must instruct Typhoon what concrete realization to use on startup. 

```objective-c

UIAssembly *uiAssembly = [[UIAssembly new] 
    activateWithCollaboratingAssemblies:@[
        [TestNetworkComponents new], 
        [PersistenceComponents new]];
  
SignUpViewController* viewController = [uiAssembly signUpViewController];


##Layered architecture

Modules can really help to promote a neat, robust architecture. But without a little discipline its still easy to get things messed up. Try to avoid heavy coupling between your modules. A good rule-of-thumb is to aim for a layered architecture, as shown below. We have infrastructure components at the bottom, with increasing levels of abstraction until we reach our top-level application assembly. Visibility between the layers can point downwards, preferably to the layer immediately below, but generally should not point upwards. (There can be occasional exceptions to this, for example in the case of delegates).

Typhoon


###Summary

  • While building the instantiating rules for your objects Typhoon always proxies collaborating assemblies.
  • Which concrete classes will fill these roles is decided later. This allows substituting one assembly for another compatible one.
  • During start-up Typhoon will collect the base classes of collaborating assemblies. You can supply another that overrides this. If you back your assemblies with protocols, you must supply the concrete types ([assembly activateWithCollaboratingAssemblies:@[a, b, c]]) at start-up.

See also: The The Typhoon Sample Application for an example showing the use of modularized assemblies.

See also: TyphoonPatcher, as described in Integration Testing is another approach to swapping out components for another implementation.