Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7010 Articles
article-image-using-googles-offerings
Packt
22 Dec 2014
8 min read
Save for later

Using Google's offerings

Packt
22 Dec 2014
8 min read
In this article by Juwal Bose, author of the book LibGDX Game Development Essentials, we will learn how to use all those features that Google has to offer. Google provides AdMob (Google Mobile Ads) to display ads to monetize our game. Google Analytics can be used to track basic app data. Google Play services can be used to implement and track global leaderboards and achievements. Before we start implementing all of these, we need to ensure the following points: Use the SDK manager to update to the latest Android SDK tools Download and install Google Play services via the SDK manager (For more resources related to this topic, see here.) Interfacing platform-specific code This chapter deals with an Android project, and much of what we will do will be specific to that platform. We need a way to detect the currently running platform to decide whether to invoke these features or not. Hence, we add a new public Boolean variable, isAndroid, in the ThrustCopter class, which is false by default. We can detect ApplicationType using the following code in the create method: switch (Gdx.app.getType()) { case Android: isAndroid=true; break; case Desktop: break; case WebGL: break; case iOS: break; default: } Now, we can check whether the game is running on an Android device using the following code: if(game.isAndroid){ ... } From the core project, we need to call the Android main class to invoke Android-specific code. We enable this using a new interface created in the core project: IActivityRequestHandler. Then, we make sure our AndroidLauncher main class implements this interface as follows: public class AndroidLauncher extends AndroidApplication implements IActivityRequestHandler{ ... initialize(new ThrustCopter(this), config); Note that we are passing this as a parameter to ThrustCopter, which provides a reference to the implemented interface. As this is Android-specific, other platforms' start classes can pass null as an argument, as we will only use this parameter on the Android platform. In the ThrustCopter class, we save the reference with the name handler, as shown in the following code: public ThrustCopter(IActivityRequestHandler IARH) {    handler=IARH; ... Visit https://github.com/libgdx/libgdx/wiki/Interfacing-with-platform-specific-code for more information. Implementing Google Analytics tracking The default implementation of Google Analytics will automatically provide the following information about your app: the number of users and sessions, session duration, operating systems, device models, and geography. To start off, we need to create a Google Analytics property and app view. Go ahead and start using Google Analytics by accessing it at https://www.google.com/analytics/web/?hl=en. Create a new account and select Mobile app and fill in the details. Once all details are entered, click on Get Tracking ID to generate a new tracking ID. The tracking ID will be unique for each account. The Google Analytics version may change in future, which means the way they are integrated may also change. Check out the Google developers portal for details at https://developers.google.com/analytics/devguides/collection/android/v4/. The AndroidManifest file needs to have the following permissions and the minSdkVersion instance should be set to 9, as follows: <uses-sdk android_minSdkVersion="9" android_targetSdkVersion="23" /> <uses-permission android_name="android.permission.INTERNET" /> <uses-permission android_name="android.permission.ACCESS_NETWORK_STATE" /> Have a look at the following Google Analytics registration form: Copy the library project at <android-sdk>/extras/google/google_play_services/libproject/google-play-services_lib/ to the location where you maintain your Android app projects. Import the library project into your Eclipse workspace. Click on File, select Import, select Android, click on Existing Android Code Into Workspace, and browse to the copy of the library project to import it. This step is important for all the Google-related services that we are about to integrate. We need to refer to this library project from our Thrust Copter-android project. Right-click on the Thrust Copter-android project and select Properties. Select the Android section, which will display a blank Library section to the right. Click on Add... to select our library project and add it as a reference. Adding tracker configuration files We can provide configuration files to create Analytics trackers. Usually, we need only one tracker, which is usually called the global tracker, to report the basic analytics data. We add the global_tracker.xml file to the res/xml folder in the Android project. Copy this file from the source provided. Update the ga_trackingId section with the new tracking ID you got on creating your application entry on the Google Analytics site. The screenName section consists of the different scenes that will be tracked. We added the MenuScene and ThrustCopterScene classes to the screenName section. This needs to be changed for each game as follows: <screenName name="com.csharks.thrustcopter.ThrustCopterScene">Thrust Copter Game</screenName>    <screenName name="com.csharks.thrustcopter.MenuScene">Thrust Copter Menu</screenName> Once the tracker XML file is in place, add the following element to the Android Manifest application part: <meta-data    android_name="com.google.android.gms.analytics.globalConfigResource"    android_resource="@xml/global_tracker" /> We need to access the tracker and report activity start, stop, and scene changes. This can be done using the following code in the AndroidLauncher class: Tracker globalTracker; Then, add the following code within the onCreate method: GoogleAnalytics analytics = GoogleAnalytics.getInstance(this); globalTracker=analytics.newTracker(R.xml.global_tracker); Now, we will move on to reporting. We added a new function in the IActivityRequestHandler interface called setTrackerScreenName(String path), which needs to be implemented as well: @Override protected void onStart(){    super.onStart();    GoogleAnalytics.getInstance(this).reportActivityStart(this); } @Override public void onStop() {    super.onStop();    GoogleAnalytics.getInstance(this).reportActivityStop(this); } @Override public void setTrackerScreenName(String path) {    globalTracker.setScreenName(path);    globalTracker.send(new HitBuilders.AppViewBuilder().build()); } We also need to report screen names as well when we switch scenes. We do this within the constructors of MenuScene and ThrustCopterScene as follows: if(game.isAndroid){ game.handler.setTrackerScreenName("com.csharks.thrustcopter.MenuScene"); } It's time to test whether everything is working. Connect your Android device and run our Android project on it. We can see that the analytics reporting is showing up in logcat. Once we have significant data, we can access the Google Analytics Web interface to analyze how the game is being played by the masses. Adding Google Mobile Ads Legacy AdMob is being renamed Google Mobile Ads, which is now linked with Google AdSense. First, we need to set up AdMob to serve ads by visiting https://www.google.com/ads/admob/index.html. Click on the Monetize section and use the Add your app manually option to set up a new banner ad. This will allot a new AdMob ad unit ID. The Ads API is also part of the Google Play services platform that we have already integrated into our Android project. We have already added as follows the necessary permissions to AndroidManifest, but we need to add the following as well: <!--This meta-data tag is required to use Google Play Services.--> <meta-data android_name="com.google.android.gms.version" android_value="@integer/google_play_services_version" /> <!--Include the AdActivity configChanges and theme. --> <activity android_name="com.google.android.gms.ads.AdActivity" android_configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android_theme="@android:style/Theme.Translucent" /> AdMob needs its own view, whereas LibGDX creates its own view when initializing. A typical way of coexisting will be our Game view in fullscreen with the Ad view overlaid. We will use RelativeLayout to arrange both views. We need to replace the initialize method with the initializeForView method, which lacks some functionality; we need to specify those manually. The onCreate method of the AndroidLauncher class has the following new code: requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);   getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);   RelativeLayout layout = new RelativeLayout(this); View gameView = initializeForView(new ThrustCopter(this), config); layout.addView(gameView);   // Add the AdMob view RelativeLayout.LayoutParams adParams =        new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,            RelativeLayout.LayoutParams.WRAP_CONTENT); adParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); adParams.addRule(RelativeLayout.CENTER_HORIZONTAL); adView = new AdView(this); adView.setAdSize(AdSize.BANNER); adView.setAdUnitId(AD_UNIT_ID); startAdvertising(); layout.addView(adView, adParams); setContentView(layout); The startAdvertising function is as follows: private void startAdvertising() { AdRequest adRequest = new AdRequest.Builder().build(); adView.loadAd(adRequest); } The IActivityRequestHandler class has a new method, showAds(boolean show), that toggles the visibility of the adView instance. The method is implemented as follows: @Override public void showAds(boolean show) { handler.sendEmptyMessage(show ? SHOW_ADS : HIDE_ADS); } Here, handler, which is used to access adView from the thread that created it, is initialized as follows: private final int SHOW_ADS = 1; private final int HIDE_ADS = 0;   protected Handler handler = new Handler()    {        @Override        public void handleMessage(Message msg) {            switch(msg.what) {                case SHOW_ADS:                {                    adView.setVisibility(View.VISIBLE);                    break;                }                case HIDE_ADS:                {                    adView.setVisibility(View.GONE);                    break;                }            }        }    }; For more information, visit https://github.com/libgdx/libgdx/wiki/Admob-in-libgdx. Alternatively, runOnUiThread can be used instead of the handleMessage method. Now, we can show ads in the menu and hide it when we switch to the game. Summary In this article, you learned how to handle platform-specific code and you also learned how to use Google Play services to integrate AdMob and Analytics. Resources for Article: Further resources on this subject: Scaling friendly font rendering with distance fields [article] Sparrow iOS Game Framework - The Basics of Our Game [article] Animations in Cocos2d-x [article]
Read more
  • 0
  • 0
  • 7042

article-image-adding-websockets
Packt
22 Dec 2014
22 min read
Save for later

Adding WebSockets

Packt
22 Dec 2014
22 min read
In this article, Michal Cmil, Michal Matloka and Francesco Marchioni, authors for the book Java EE 7 Development with WildFly we will explore the new possibilities that they provide to a developer. In our ticket booking applications, we already used a wide variety of approaches to inform the clients about events occurring on the server side. These include the following: JSF polling Java Messaging Service (JMS) messages REST requests Remote EJB requests All of them, besides JMS, were based on the assumption that the client will be responsible for asking the server about the state of the application. In some cases, such as checking whether someone else has not booked a ticket during our interaction with the application, this is a wasteful strategy; the server is in the position to inform clients when it is needed. What's more, it feels like the developer must hack the HTTP protocol to get a notification from a server to the client. This is a requirement that has to be implemented in most web applications, and therefore, deserves a standardized solution that can be applied by the developers in multiple projects without much effort. WebSockets are changing the game for developers. They replace the request-response paradigm in which the client always initiates the communication with a two-point bidirectional messaging system. After the initial connection, both sides can send independent messages to each other as long as the session is alive. This means that we can easily create web applications that will automatically refresh their state with up-to-date data from the server. You probably have already seen this kind of behavior in Google Docs or live broadcasts on news sites. Now we can achieve the same effect in a simpler and more efficient way than in earlier versions of Java Enterprise Edition. In this article, we will try to leverage these new, exciting features that come with WebSockets in Java EE 7 thanks to JSR 356 (https://jcp.org/en/jsr/detail?id=356) and HTML5. In this article, you will learn the following topics: How WebSockets work How to create a WebSocket endpoint in Java EE 7 How to create an HTML5/AngularJS client that will accept push notifications from an application deployed on WildFly (For more resources related to this topic, see here.) An overview of WebSockets A WebSocket session between the client and server is built upon a standard TCP connection. Although the WebSocket protocol has its own control frames (mainly to create and sustain the connection) coded by the Internet Engineering Task Force in the RFC 6455 (http://tools.ietf.org/html/rfc6455), the peers are not obliged to use any specific format to exchange application data. You may use plaintext, XML, JSON, or anything else to transmit your data. As you probably remember, this is quite different from SOAP-based WebServices, which had bloated specifications of the exchange protocol. The same goes for RESTful architectures; we no longer have the predefined verb methods from HTTP (GET, PUT, POST, and DELETE), status codes, and the whole semantics of an HTTP request. This liberty means that WebSockets are pretty low level compared to the technologies that we have used up to this point, but thanks to this, the communication overhead is minimal. The protocol is less verbose than SOAP or RESTful HTTP, which allows us to achieve higher performance. This, however, comes with a price. We usually like to use the features of higher-level protocols (such as horizontal scaling and rich URL semantics), and with WebSockets, we would need to write them by hand. For standard CRUD-like operations, it would be easier to use a REST endpoint than create everything from scratch. What do we get from WebSockets compared to the standard HTTP communication? First of all, a direct connection between two peers. Normally, when you connect to a web server (which can, for instance, handle a REST endpoint), every subsequent call is a new TCP connection, and your machine is treated like it is a different one every time you make a request. You can, of course, simulate a stateful behavior (so that the server will recognize your machine between different requests) using cookies and increase the performance by reusing the same connection in a short period of time for a specific client, but basically, it is a workaround to overcome the limitations of the HTTP protocol. Once you establish a WebSocket connection between a server and client, you can use the same session (and underlying TCP connection) during the whole communication. Both sides are aware of it and can send data independently in a full-duplex manner (both sides can send and receive data simultaneously). Using plain HTTP, there is no way for the server to spontaneously start sending data to the client without any request from its side. What's more, the server is aware of all of its connected WebSocket clients, and can even send data between them! The current solution that includes trying to simulate real-time data delivery using HTTP protocol can put a lot of stress on the web server. Polling (asking the server about updates), long polling (delaying the completion of a request to the moment when an update is ready), and streaming (a Comet-based solution with a constantly open HTTP response) are all ways to hack the protocol to do things that it wasn't designed for and have their own limitations. Thanks to the elimination of unnecessary checks, WebSockets can heavily reduce the number of HTTP requests that have to be handled by the web server. The updates are delivered to the user with a smaller latency because we only need one round-trip through the network to get the desired information (it is pushed by the server immediately). All of these features make WebSockets a great addition to the Java EE platform, which fills the gaps needed to easily finish specific tasks, such as sending updates, notifications, and orchestrating multiple client interactions. Despite these advantages, WebSockets are not intended to replace REST or SOAP WebServices. They do not scale so well horizontally (they are hard to distribute because of their stateful nature), and they lack most of the features that are utilized in web applications. URL semantics, complex security, compression, and many other features are still better realized using other technologies. How does WebSockets work To initiate a WebSocket session, the client must send an HTTP request with an Upgrade: websocket header field. This informs the server that the peer client has asked the server to switch to the WebSocket protocol. You may notice that the same happens in WildFly for Remote EJBs; the initial connection is made using an HTTP request, and is later switched to the remote protocol thanks to the Upgrade mechanism. The standard Upgrade header field can be used to handle any protocol, other than HTTP, which is accepted by both sides (the client and server). In WildFly, this allows you to reuse the HTTP port (80/8080) for other protocols and therefore minimise the number of required ports that should be configured. If the server can "understand" the WebSocket protocol, the client and server then proceed with the handshaking phase. They negotiate the version of the protocol, exchange security keys, and if everything goes well, the peers can go to the data transfer phase. From now on, the communication is only done using the WebSocket protocol. It is not possible to exchange any HTTP frames using the current connection. The whole life cycle of a connection can be summarized in the following diagram: A sample HTTP request from a JavaScript application to a WildFly server would look similar to this: GET /ticket-agency-websockets/tickets HTTP/1.1 Upgrade: websocket Connection: Upgrade Host: localhost:8080 Origin: http://localhost:8080Pragma: no-cache Cache-Control: no-cache Sec-WebSocket-Key: TrjgyVjzLK4Lt5s8GzlFhA== Sec-WebSocket-Version: 13 Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36 Cookie: [45 bytes were stripped] We can see that the client requests an upgrade connection with WebSocket as the target protocol on the URL /ticket-agency-websockets/tickets. It additionally passes information about the requested version and key. If the server supports the request protocol and all the required data is passed by the client, then it would respond with the following frame: HTTP/1.1 101 Switching Protocols X-Powered-By: Undertow 1 Server: Wildfly 8 Origin: http://localhost:8080 Upgrade: WebSocket Sec-WebSocket-Accept: ZEAab1TcSQCmv8RsLHg4RL/TpHw= Date: Sun, 13 Apr 2014 17:04:00 GMT Connection: Upgrade Sec-WebSocket-Location: ws://localhost:8080/ticket-agency-websockets/tickets Content-Length: 0 The status code of the response is 101 (switching protocols) and we can see that the server is now going to start using the WebSocket protocol. The TCP connection initially used for the HTTP request is now the base of the WebSocket session and can be used for transmissions. If the client tries to access a URL, which is only handled by another protocol, then the server can ask the client to do an upgrade request. The server uses the 426 (upgrade required) status code in such cases. The initial connection creation has some overhead (because of the HTTP frames that are exchanged between the peers), but after it is completed, new messages have only 2 bytes of additional headers. This means that when we have a large number of small messages, WebSocket will be an order of magnitude faster than REST protocols simply because there is less data to transmit! If you are wondering about the browser support of WebSockets, you can look it up at http://caniuse.com/websockets. All new versions of major browsers currently support WebSockets; the total coverage is estimated (at the time of writing) at 74 percent. You can see this in the following screenshot: After this theoretical introduction, we are ready to jump into action. We can now create our first WebSocket endpoint! Creating our first endpoint Let's start with a simple example: package com.packtpub.wflydevelopment.chapter8.boundary; import javax.websocket.EndpointConfig; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; @ServerEndpoint("/hello") public class HelloEndpoint {    @OnOpen    public void open(Session session, EndpointConfig conf) throws IOException {        session.getBasicRemote().sendText("Hi!");    } } Java EE 7 specification has taken into account developer friendliness, which can be clearly seen in the given example. In order to define your WebSocket endpoint, you just need a few annotations on a Plain Old Java Object (POJO). The first annotation @ServerEndpoint("/hello") defines a path to your endpoint. It's a good time to discuss the endpoint's full address. We placed this sample in the application named ticket-agency-websockets. During the deployment of application, you can spot information in the WildFly log about endpoints creation, as shown in the following command line: 02:21:35,182 INFO [io.undertow.websockets.jsr] (MSC service thread 1-7) UT026003: Adding annotated server endpoint class com.packtpub.wflydevelopment.chapter8.boundary.FirstEndpoint for path /hello 02:21:35,401 INFO [org.jboss.resteasy.spi.ResteasyDeployment] (MSC service thread 1-7) Deploying javax.ws.rs.core.Application: class com.packtpub.wflydevelopment.chapter8.webservice.JaxRsActivator$Proxy$_$$_WeldClientProxy 02:21:35,437 INFO [org.wildfly.extension.undertow] (MSC service thread 1-7) JBAS017534: Registered web context: /ticket-agency-websockets The full URL of the endpoint is ws://localhost:8080/ticket-agency-websockets/hello, which is just a concatenation of the server and application address with an endpoint path on an appropriate protocol. The second used annotation @OnOpen defines the endpoint behavior when the connection from the client is opened. It's not the only behavior-related annotation of the WebSocket endpoint. Let's look to the following table: Annotation Description @OnOpen The connection is open. With this annotation, we can use the Session and EndpointConfig parameters. The first parameter represents the connection to the user and allows further communication. The second one provides some client-related information. @OnMessage This annotation is executed when a message from the client is being received. In such a method, you can just have Session and for example, the String parameter, where the String parameter represents the received message. @OnError There are bad times when an error occurs. With this annotation, you can retrieve a Throwable object apart from standard Session. @OnClose When the connection is closed, it is possible to get some data concerning this event in the form of the CloseReason type object.  There is one more interesting line in our HelloEndpoint. Using the Session object, it is possible to communicate with the client. This clearly shows that in WebSockets, two-directional communication is easily possible. In this example, we decided to respond to a connected user synchronously (getBasicRemote()) with just a text message Hi! (sendText (String)). Of course, it's also possible to communicate asynchronously and send, for example, sending binary messages using your own binary bandwidth saving protocol. We will present some of these processes in the next example. Expanding our client application It's time to show how you can leverage the WebSocket features in real life. Since we're just adding a feature to our previous app, we will only describe the changes we will introduce to it. In this example, we would like to be able to inform all current users about other purchases. This means that we have to store information about active sessions. Let's start with the registry type object, which will serve this purpose. We can use a Singleton session bean for this task, as shown in the following code: @Singleton public class SessionRegistry {    private final Set<Session> sessions = new HashSet<>();    @Lock(LockType.READ)    public Set<Session> getAll() {        return Collections.unmodifiableSet(sessions);    }    @Lock(LockType.WRITE)    public void add(Session session) {        sessions.add(session);    }    @Lock(LockType.WRITE)    public void remove(Session session) {        sessions.remove(session);    } } We could use Collections.synchronizedSet from standard Java libraries about container-based concurrency. In SessionRegistry, we defined some basic methods to add, get, and remove sessions. For the sake of collection thread safety during retrieval, we return an unmodifiable view. We defined the registry, so now we can move to the endpoint definition. We will need a POJO, which will use our newly defined registry as shown: @ServerEndpoint("/tickets")public class TicketEndpoint {   @Inject   private SessionRegistry sessionRegistry;   @OnOpen   public void open(Session session, EndpointConfig conf) {       sessionRegistry.add(session);   }   @OnClose   public void close(Session session, CloseReason reason) {       sessionRegistry.remove(session);   }   public void send(@Observes Seat seat) {       sessionRegistry.getAll().forEach(session -> session.getAsyncRemote().sendText(toJson(seat)));   }   private String toJson(Seat seat) {       final JsonObject jsonObject = Json.createObjectBuilder()               .add("id", seat.getId())             .add("booked", seat.isBooked())               .build();       return jsonObject.toString();   } } Our endpoint is defined in the /tickets address. We injected a SessionRepository to our endpoint. During @OnOpen, we add Sessions to the registry, and during @OnClose, we just remove them. Message sending is performed on the CDI event (the @Observers annotation), which is already fired in our code during TheatreBox.buyTicket(int). In our send method, we retrieve all sessions from SessionRepository, and for each of them, we asynchronously send information about booked seats. We don't really need information about all the Seat fields to realize this feature. Instead, we decided to use a minimalistic JSON object, which provides only the required data. To do this, we used the new Java API for JSON Processing (JSR-353). Using a fluent-like API, we're able to create a JSON object and add two fields to it. Then, we just convert JSON to the string, which is sent in a text message. Because in our example we send messages in response to a CDI event, we don't have (in the event handler) an out-of-the-box reference to any of the sessions. We have to use our sessionRegistry object to access the active ones. However, if we would like to do the same thing but, for example, in the @OnMessage method, then it is possible to get all active sessions just by executing the session.getOpenSessions() method. These are all the changes required to perform on the backend side. Now, we have to modify our AngularJS frontend to leverage the added feature. The good news is that JavaScript already includes classes that can be used to perform WebSocket communication! There are a few lines of code we have to add inside the module defined in the seat.js file, which are as follows: var ws = new WebSocket("ws://localhost:8080/ticket-agency-websockets/tickets"); ws.onmessage = function (message) {    var receivedData = message.data;    var bookedSeat = JSON.parse(receivedData);    $scope.$apply(function () {        for (var i = 0; i < $scope.seats.length; i++) {            if ($scope.seats[i].id === bookedSeat.id) {                $scope.seats[i].booked = bookedSeat.booked;                break;            }        }    }); }; The code is very simple. We just create the WebSocket object using the URL to our endpoint, and then we define the onmessage function in that object. During the function execution, the received message is automatically parsed from the JSON to JavaScript object. Then, in $scope.$apply, we just iterate through our seats, and if the ID matches, we update the booked state. We have to use $scope.$apply because we are touching an Angular object from outside the Angular world (the onmessage function). Modifications performed on $scope.seats are automatically visible on the website. With this, we can just open our ticket booking website in two browser sessions, and see that when one user buys a ticket, the second users sees almost instantly that the seat state is changed to booked. We can enhance our application a little to inform users if the WebSocket connection is really working. Let's just define onopen and onclose functions for this purpose: ws.onopen = function (event) {    $scope.$apply(function () {        $scope.alerts.push({            type: 'info',            msg: 'Push connection from server is working'        });    }); }; ws.onclose = function (event) {    $scope.$apply(function () {        $scope.alerts.push({            type: 'warning',            msg: 'Error on push connection from server '        });    }); }; To inform users about a connection's state, we push different types of alerts. Of course, again we're touching the Angular world from the outside, so we have to perform all operations on Angular from the $scope.$apply function. Running the described code results in the notification, which is visible in the following screenshot:   However, if the server fails after opening the website, you might get an error as shown in the following screenshot:   Transforming POJOs to JSON In our current example, we transformed our Seat object to JSON manually. Normally, we don't want to do it this way; there are many libraries that will do the transformation for us. One of them is GSON from Google. Additionally, we can register an encoder/decoder class for a WebSocket endpoint that will do the transformation automatically. Let's look at how we can refactor our current solution to use an encoder. First of all, we must add GSON to our classpath. The required Maven dependency is as follows: <dependency>    <groupId>com.google.code.gson</groupId>    <artifactId>gson</artifactId>    <version>2.3</version> </dependency> Next, we need to provide an implementation of the javax.websocket.Encoder.Text interface. There are also versions of the javax.websocket.Encoder.Text interface for binary and streamed data (for both binary and text formats). A corresponding hierarchy of interfaces is also available for decoders (javax.websocket.Decoder). Our implementation is rather simple. This is shown in the following code snippet: public class JSONEncoder implements Encoder.Text<Object> {    private Gson gson;    @Override    public void init(EndpointConfig config) {        gson = new Gson(); [1]    }    @Override    public void destroy() {        // do nothing    }    @Override    public String encode(Object object) throws EncodeException {        return gson.toJson(object); [2]    } } First, we create an instance of GSON in the init method; this action will be executed when the endpoint is created. Next, in the encode method, which is called every time, we send an object through an endpoint. We use JSON command to create JSON from an object. This is quite concise when we think how reusable this little class is. If you want more control on the JSON generation process, you can use the GsonBuilder class to configure the Gson object before creation. We have the encoder in place. Now it's time to alter our endpoint: @ServerEndpoint(value = "/tickets", encoders={JSONEncoder.class})[1] public class TicketEndpoint {    @Inject    private SessionRegistry sessionRegistry;    @OnOpen    public void open(Session session, EndpointConfig conf) {        sessionRegistry.add(session);    }    @OnClose    public void close(Session session, CloseReason reason) {        sessionRegistry.remove(session);    }    public void send(@Observes Seat seat) {        sessionRegistry.getAll().forEach(session -> session.getAsyncRemote().sendObject(seat)); [2]    } } The first change is done on the @ServerEndpoint annotation. We have to define a list of supported encoders; we simply pass our JSONEncoder.class wrapped in an array. Additionally, we have to pass the endpoint name using the value attribute. Earlier, we used the sendText method to pass a string containing a manually created JSON. Now, we want to send an object and let the encoder handle the JSON generation; therefore, we'll use the getAsyncRemote().sendObject() method. And that's all. Our endpoint is ready to be used. It will work the same as the earlier version, but now our objects will be fully serialized to JSON, so they will contain every field, not only id and booked. After deploying the server, you can connect to the WebSocket endpoint using one of the Chrome extensions, for instance, the Dark WebSocket terminal from the Chrome store (use the ws://localhost:8080/ticket-agency-websockets/tickets address). When you book tickets using the web application, the WebSocket terminal should show something similar to the output shown in the following screenshot:   Of course, it is possible to use different formats other than JSON. If you want to achieve better performance (when it comes to the serialization time and payload size), you may want to try out binary serializers such as Kryo (https://github.com/EsotericSoftware/kryo). They may not be supported by JavaScript, but may come in handy if you would like to use WebSockets for other clients too. Tyrus (https://tyrus.java.net/) is a reference implementation of the WebSocket standard for Java; you can use it in your standalone desktop applications. In that case, besides the encoder (which is used to send messages), you would also need to create a decoder, which can automatically transform incoming messages. An alternative to WebSockets The example we presented in this article is possible to be implemented using an older, lesser-known technology named Server-Sent Events (SSE). SSE allows one-way communication from the server to client over HTTP. It is much simpler than WebSockets but has a built-in support for things such as automatic reconnection and event identifiers. WebSockets are definitely more powerful, but are not the only way to pass events, so when you need to implement some notifications from the server side, remember about SSE. Another option is to explore the mechanisms oriented around the Comet techniques. Multiple implementations are available and most of them use different methods of transportation to achieve their goals. A comprehensive comparison is available at http://cometdaily.com/maturity.html. Summary In this article, we managed to introduce the new low-level type of communication. We presented how it works underneath and compares to SOAP and REST. We also discussed how the new approach changes the development of web applications. Our ticket booking application was further enhanced to show users the changing state of the seats using push-like notifications. The new additions required very little code changes in our existing project when we take into account how much we are able to achieve with them. The fluent integration of WebSockets from Java EE 7 with the AngularJS application is another great showcase of flexibility, which comes with the new version of the Java EE platform. Resources for Article: Further resources on this subject: Using the WebRTC Data API [Article] Implementing Stacks using JavaScript [Article] Applying WebRTC for Education and E-learning [Article]
Read more
  • 0
  • 0
  • 1989

article-image-cloud-distribution-points
Packt
22 Dec 2014
8 min read
Save for later

Cloud distribution points

Packt
22 Dec 2014
8 min read
In this article by Vangel Krstevski, author of Mastering System Center Configuration manager, we will learn that a cloud distribution point is a fallback distribution point for the Configuration Manager clients and supports most of the content types. To create a cloud distribution point, you need a Windows Azure subscription, a DNS server, and certificates. For your production environment, you can use the Azure pricing calculator to calculate your subscription fee at http://azure.microsoft.com/en-us/pricing/calculator/?scenario=full. (For more resources related to this topic, see here.) Starting with System Center Configuration Manager SP1, you can use a Windows Azure cloud service to host a distribution point server. When you deploy a cloud-based distribution point server, you configure the client settings and through them, enable users and devices to access the content. You also have to specify a primary site that will manage the content transfer to the cloud-based distribution point. Additionally, you need to specify the thresholds for the amount of content that you want to store on the distribution point and the amount of content that you want to enable clients to transfer from the distribution point. Based on these thresholds, the Configuration Manager can raise alerts that warn you when the combined amount of content that you have stored on the distribution point is near the specified storage amount, or when the transfer of data by the clients is close to the threshold that you defined. The following features are supported by both on-premise and cloud-based distribution points: The management of cloud-based distribution points individually or as members of distribution point groups A cloud-based distribution point can be used as a fallback content location You receive support for both intranet- and Internet-based clients A cloud-based distribution point provides the following additional benefits: The content that is sent to the cloud-based distribution point is encrypted by Configuration Manager before the Configuration Manager sends it to Windows Azure In Windows Azure, you can manually scale the cloud service to meet the changing demands for content request by clients, without the requirement to install and provision additional distribution points The cloud-based distribution point supports the download of content by clients that are configured for Windows BranchCache A cloud-based distribution point has the following limitations: You cannot use a cloud-based distribution point to host software update packages. You cannot use a cloud-based distribution point for PXE-enabled or multicast-enabled deployments. Clients are not offered a cloud-based distribution point as a content location for a task sequence that is deployed using the Download content locally when needed by running task sequence deployment option. However, task sequences that are deployed using the Download all content locally before starting task sequence deployment option can use a cloud-based distribution point as a valid content location. A cloud-based distribution point does not support packages that run from the distribution point. All content must be downloaded by the client and then run locally. A cloud-based distribution point does not support the streaming of applications by using Application Virtualization or similar programs. Prestaged content is not supported. The primary site Distribution Manager that is used for distribution point management does all the content transfers to the distribution point. A cloud-based distribution point cannot be configured as a pull-distribution point. To configure a cloud-based distribution point, follow these steps: Create a management certificate and install it on the site server. This certificate establishes a trust relationship between the site server and Windows Azure. Create a cloud distribution point service certificate and install it on the site server. Create a Windows Azure subscription and import the previously created management certificate in Windows Azure through the management portal. Install a cloud distribution point role in Configuration Manager. Set up the client settings to allow Configuration Manager clients to use the cloud-based distribution point. Create a record in your DNS with the IP address of the cloud distribution point. Cloud distribution points – prerequisites A cloud-based distribution point has the following prerequisites: A Windows Azure subscription. A self-signed or PKI management certificate for communication between the Configuration Manager primary site server and the Windows Azure Cloud Service. A service certificate (PKI) that Configuration Manager clients will use in order to connect to the cloud-based distribution points and also to download content from these distribution points using secure transfer or HTTPS. Before users and devices can access the content on a cloud-based distribution point, a device or a user has to receive the client setting for cloud services of Allow access to cloud distribution points set to Yes. By default, this value is set to No. A client must be able to resolve the name of the cloud service, which requires a Domain Name System (DNS) alias and CNAME record, in your DNS namespace. A client must have Internet access in order to use the cloud-based distribution point. Creating certificates Use the following link to create the needed certificates for the Cloud distribution point creation: http://technet.microsoft.com/en-us/library/230dfec0-bddb-4429-a5db-30020e881f1e#BKMK_clouddp2008_cm2012 Importing the certificates in Windows Azure First, what you need to do is log in to your Windows Azure subscription. To do this, you have to perform the following steps: Go to https://manage.windowsazure.com. After you log in, go to SETTINGS from the menu on the left-hand side, as shown in the following screenshot: Click on MANAGEMENT CERTIFICATES, as shown here: Upload the management certificate that you created for the site server, as shown in the following screenshot: After the import, you will be able to see the certificate in the list of imported MANAGEMENT CERTIFICATES, as shown here: Creating the cloud distribution point In order to create the cloud distribution point, you have to do the following: Start the System Center Configuration Manager console. Navigate to Administration | Hierarchy Configuration | Cloud Services | Cloud Distribution Points, as shown in the following screenshot: From the ribbon bar, click on Create Cloud Distribution Point. On the General page, you have to enter the Windows Azure subscription ID. You can find your Windows Azure subscription ID in the Settings section of the Windows Azure management portal. Click on Browse… to select the certificate that you created for the site server, as shown here: On the Settings page, select the region, for example, West Europe. Click on Browse… and import the cloud distribution point service certificate, as shown in the next screenshot: On the Alerts page, you can configure the settings about the threshold levels of your cloud distribution point. These levels are important because they can alert you when levels drop below a certain level that you have defined. For the purpose of this project, just click on Next: Review all the settings in the Summary page and click on Next to start the cloud distribution point's installation process. After the Cloud distribution point is created, you will be able to see it in the list of Cloud Distribution Points in the System Center Configuration Manager console, as shown here: Configuring DNS for the cloud distribution point For clients to download content from a cloud distribution point, a DNS record must exist for the cloud distribution point's IP address. You can do this by adding a CNAME record in your DNS server that points to the site URL of the Windows Azure Cloud Service. The FQDN of your Windows Azure Cloud Service can be found by proceeding with the following steps: Log in to your Windows Azure subscription. Select Cloud Services from the menu on the left-hand side. From the list of cloud services, click on the service name that represents your cloud distribution point. This will open the cloud service dashboard. The site URL information can be found on the right-hand side of the dashboard, as shown in the following screenshot: Open your DNS server and create the CNAME record. For the alias name, enter CloudDP and for the FQDN of the target host, enter the site URL of your Windows Azure, shown as follows: Summary In this article we saw that the main benefit of a cloud distribution point is that it can work as a backup distribution point. We also saw how we can use System Center Configuration Manager 2012 R2 to deliver applications to different mobile device platforms. We also learned how to connect the Configuration Manager to Windows Intune in order to provide mobile device management and application deployment and to ensure secure and managed access to company resources. Resources for Article: Further resources on this subject: Configuring Roles for High Availability [article] Configuring and Managing the Mailbox Server Role [article] Integration with System Center Operations Manager 2012 SP1 [article]
Read more
  • 0
  • 0
  • 2876

article-image-maintaining-your-gitlab-instance
Packt
22 Dec 2014
14 min read
Save for later

Maintaining Your GitLab Instance

Packt
22 Dec 2014
14 min read
In this article by Jeroen van Baarsen, the author of the book GitLab Cookbook, we will see how we can manage GitLab. We look at procedures to update GitLab and also concentrate on troubleshooting problems faced. Updating an Omnibus installation Updating GitLab from a source installation Troubleshooting your GitLab installation Creating a backup Restoring a backup Importing Git repositories (For more resources related to this topic, see here.) Introduction When running your GitLab installation, you need to update it every once in a while. GitLab is released every 22nd day of the month, so around the end of the month would be a nice time to update! The releases on the 22nd day are well tested, as gitlab.com is using the release candidates in production all the time. This way, you can be sure that the release meets the standards of the GitLab team! In this chapter, we will take a look at how you can update your GitLab server and how you can create backups and restore them. If you want to know what has changed in the new release, you can take a look at the change log provided in the repository for GitLab at https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG. Updating an Omnibus installation In this article, we will take a look at how you can update your GitLab installation when you install it via the Omnibus package. In this article, I'll make the assumption that you're using Ubuntu 12.04. You can use other Linux distributions; the steps for these can be found at about.gitlab.com. How to do it… Let's update the Omnibus installation with the following steps: Log in to your server via SSH. Stop the unicorn server: $ sudo gitlab-ctl stop unicorn Stop the background job server: $ sudo gitlab-ctl stop sidekiq Create a backup in case the upgrade fails: $ sudo gitlab-rake gitlab:backup:create Download the package from the GitLab website (https://about.gitlab.com/downloads/): $ wget https://downloads-packages.s3.amazonaws.com/ubuntu-12.04/gitlab_7.1.1-omnibus.1-1_amd64.deb Install the new package (change the x.x.x part to the correct version number from your download): sudo dpkg -i gitlab_x.x.x-omnibus.xxx.deb Reconfigure GitLab: sudo gitlab-ctl reconfigure Restart all the services: sudo gitlab-ctl restart How it works… On the 22nd day of every month, a new version of GitLab is released. This also includes the Omnibus package. As the installation of Omnibus based on GitLab does not take very long, the GitLab team has decided to install a new version of GitLab and preserve the old data of the old installation; this way, you don't go over an update process, but you will be guided through the installation process as if you're installing a new GitLab instance. So, when you're updating an Omnibus-based installation, you're not really updating but rather installing a newer version and reconfiguring it to use the old data. One thing you have to keep in mind is that making backups is very important. As any update can go wrong at some level, it's a good feeling to know that when stuff goes wrong, you always have a backup that you can use to get back up and running as quickly as possible. Updating GitLab from a source installation Updating used to be a lot of work; you had to open the update document to find out that you need to perform about 15 steps to upgrade your GitLab installation. To tackle this issue, the GitLab team has created a semiautomatic upgrader. When you run the upgrader, it will check whether there is a new minor version. If there is, it will start the upgrade process for you. It will perform database migrations and update config files for you. How to do it… Upgrade your source installation with the following steps: Log in to your server using SSH. We start with creating a backup just in case something goes wrong. Go to the folder of your GitLab instance: $ cd /home/git/gitlab Create the backup; this might take a little while depending on the amount of repositories and the size of each individual repository: $ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production Stop the server: $ sudo service gitlab stop Run the upgrader: $ if [ -f bin/upgrade.rb ]; then sudo -u git -Hruby bin/upgrade.rb; else sudo -u git -Hruby script/upgrade.rb; fi Start the application: $ sudo service gitlab start && sudo service nginx restart Check whether everything went fine. We can use the self-check GitLab ships with: $ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production If the GitLab check indicates that we need to upgrade the GitLab shell, we can do this by performing the following steps. Go to the shell directory: $ cd /home/git/gitlab-shell Fetch the latest code: $ sudo -u git -H git fetch Run the following command to set the pointer to the latest shell release. Change 1.9.4 to the current version number. You can find this number in the output of the check we did in step 9: sudo -u git -H git checkout v1.9.4 How it works… It is highly recommended that you create a backup before you run the upgrader as every update can break something in your installation. It's a good feeling to know that when all goes wrong, you're prepared and can roll back the upgrade using the backup. Troubleshooting your GitLab installation When your GitLab instance does not work the way you expect it to work, it's nice to have a way to check what parts of your installation are not working properly. In this article, we will take a look at the self-diagnostic tools provided by GitLab. How to do it… Learn how to troubleshoot your GitLab server with the following steps: Log in to your server using SSH. The first case shows troubleshooting in the case of a GitLab source installation. Go to your gitlab folder: $ cd /home/git/gitlab To autodiagnose your installation, run the following command: $ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production When there is a problem with your GitLab installation, it will be outputted in red text, as shown in the following screenshot: The solution for the problem is also given; just follow the explanation given by the problem you walk into. In this case, we have to run the following command: $ sudo-u git -h bundle exec rake gitlab:satellites:create RAILS_ENV=production If everything is green, your installation is in great shape! The next few steps concentrate on troubleshooting in the case of the Gitlab Omnibus installation. Run the following command: $ sudo gitlab-rake gitlab:check When you have any problem with your GitLab installation, this will be outputted; also, the possible solution will be shown. The solution that is given for this problem is the solution for the source installation. To fix this issue in the Omnibus installation, we need to alter the command a little. You have to replace the $ sudo-u git -h bundle exec rake part with $ sudo gitlab-rake. So, the command will look as follows: $ sudo gitlab-rake gitlab:satellites:create RAILS_ENV=production If everything is green, your installation is in top shape! In case you need to view the logs, you can run the following command: $ sudo gitlab-ctl tail To exit the log flow, use Ctrl + C. How it works… When you think your GitLab installation might not be in a good shape or you think you've found a bug, it's always a good idea to run the self-diagnostics for GitLab. It will tell you whether you've configured GitLab correctly and whether everything is still up to date. Here is a list of what will be checked: Is your database config correct? If your database is still running SQLite instead of PostgreSQL or MySQL, it will give you a warning. Are all the user groups configured correctly? Is the GitLab config present and up to date? Are the logs writable? Is the tmp directory writable? Is the init script present and up to date? Are all the projects namespaced? This is important if you've upgraded from an old version. Are all the satellites present? Is your Redis up to date? Is the correct Ruby version being used? Is the correct Git version being used? This is the first place you should start when walking into problems. If this does not give you the correct answers, you can open an issue on the GitLab repository (https://gitlab.com/gitlab-org/gitlab-ce). Make sure you post the output of this check as well, as you will most likely be asked to post it anyway. Creating a backup It's important that you have your source code secured so that when your laptop breaks down—or even worse, the office got destroyed—the most valuable part of your company (besides the employees, of course) is still intact. You've already taken an important step into the right direction; you set up a Git server so that your source code has multiple places to live. However, what if that server breaks down? This is where backups come into play! GitLab Omnibus makes it really easy to create backups. With just a simple command, everything in your system gets backed up: the repositories as well as the databases. It's all packed in a tar ball, so you can store it elsewhere, for example, Amazon S3 or just another server somewhere else. In this article, we not only created a backup, but also created a schema to automatically back up the creation using crontabs. This way, you can rest assured that all of your code gets backed up every night. How to do it… In the following steps, we will set up the backups for GitLab: First, log in to your server using SSH. The first few steps concentrate on the GitLab source installation. Go to your gitlab folder: cd /home/git/gitlab Run the backup command: bundle exec rake gitlab:backup:create RAILS_ENV=production The backup will now run. This might take a while depending on the number of repositories and the size of each repository. The backups will be stored in the /home/git/gitlab/tmp/backups directory. Having to create a backup by hand everyday is no fun, so let's automate the creation of backups using a cronjob file. Run the following command to open the cronjob file: $ sudo -u git crontab -e Add the following content to the end of the file: 0 2 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production Save the file, and the backups will be created everyday at 2 A.M. The next few steps talk about the GitLab Omnibus installation. To create the backup, run the following command: $ sudo gitlab-rake gitlab:backup:create A backup is now created in the $ /var/opt/gitlab/backups directory. Let's verify that our backup is actually there: $ ls /var/opt/gitlab/backups/ You should see at least one filename, such as 1404647816_gitlab_backup.tar. The number in the filename is a timestamp, so this might differ in your case. Now we know how to create the backup. Let's automate this via a cronjob file. Run the following command as the root user: $ crontab -e Add the following code to the end of the file to have the backup run every day at 2 A.M.: 0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create After you save the file, a backup will be created every day at 2 A.M. This is great, but there is a tiny catch; if you have the backups run for too long, it will take up all of your disk space. Let's fix this! Open this file location: /etc/gitlab/gitlab.rb. Let's have the backups only last for a week. After that, they will be destroyed. 7 days is 604,800 seconds. Add the following code to the bottom of the file: gitlab_rails['backup_keep_time'] = 604800 To have the changes take effect, we have to tell GitLab to reconfigure itself. Run the following command: $ sudo gitlab-ctl reconfigure Restoring a backup If your server breaks down, it is nice to have a backup. However, it's a pain when it's a full day's work to restore that backup; it's just a waste of time. Luckily, GitLab makes it super easy to restore the backup. Retrieve the backup from your external Amazon S3 storage or just your external hard drive, copy the file to your server, and run the backup restore command. It won't get any easier! Getting ready Make sure you have a recent backup of your GitLab instance. After you restore the backup, all the data created between the backup creation and the restoration of your backup will be lost. How to do it… Let's restore a backup using the following steps: Start with a login to your server using SSH. The next few steps concentrate on the GitLab source installation. Go to your gitlab folder: $ cd /home/git/gitlab Go to the backup folder of GitLab: $ cd /home/git/gitlab/tmp/backups Look at the filename of the most recent file and note the number that the filename starts with. Go back to your GitLab folder: $ cd /home/git/gitlab Now, run the following command and replace the 1234567 part with the number you took from the latest backup filename: $ bundle exec rake gitlab:backup:restore RAILS_ENV=production BACKUP=1234567 The next few steps concentrate on the GitLab Omnibus installation. Make sure your backup file is located at /var/opt/gitlab/backups: $ cp 1407564013_gitlab_backup.tar /var/opt/gitlab/backups/ Before we can restore the backup, we need to stop our instance. First, stop GitLab itself: $ sudo gitlab-ctl stop unicorn Next, stop the background worker: $ sudo gitlab-ctl stop sidekiq Now, we will restore our backup. You need to provide the timestamp of the backup you want to restore. The timestamp is the long number before the filename. Warning: this will overwrite all the data in your database! The following command depicts this: $ sudo gitlab-rake gitlab:backup:restore BACKUP=TIMESTAMP_OF_BACKUP Restoring the actual backup might take a little while depending on the size of your database. Restart your GitLab server again: $ sudo gitlab-ctl start Importing an existing repository It's quite simple to import your repositories from somewhere else. All you need to do is create a new project and select the repository to be imported. In this article, we will take a look at how this is done. For this article, we will import the repository hosted on GitHub at https://github.com/gitlabhq/gitlab-shell. How to do it… In the following steps, we will import a repository: Log in to your GitLab instance. Click on New project. Enter the project name as GitLab Shell. Click on Import existing repository?. Enter the URL for the repository we want to import: https://github.com/gitlabhq/gitlab-shell Now, click on Create Project. Importing the existing repository might take a while depending on the size of the repository. After the importing is done, you will be redirected to the project page. Let's check whether it's actually an imported repository. Click on the Network menu item. If everything is fine, you should see the graph in the following screenshot: How it works… There is really nothing magical about importing a repository. All GitLab does is clone the URL you give it to its own satellite. After this is done, the satellite will be linked to your project, and you're done! What if your repository is private and not publicly accessible? You can import it by adding the user credentials in the URL. Don't worry; this information is not stored anywhere! So, if we have the same repository as the one we used earlier but it has credentials, it will look like what is shown in https://username:password@github.com/gitlabhq/gitlab-shell. Summary In this article, we learned about the update processes for GitLab. We also got a gist of the ways that we can use to troubleshoot problems faced. The article explains how we can restore and back up an existing repository. Resources for Article: Further resources on this subject: Searching and Resolving Conflicts [Article] Issues and Wikis in GitLab [Article] Configuration [Article]
Read more
  • 0
  • 1
  • 3471

article-image-recursive-directives
Packt
22 Dec 2014
13 min read
Save for later

Recursive directives

Packt
22 Dec 2014
13 min read
In this article by Matt Frisbie, the author of AngularJS Web Application Development Cookbook, we will see recursive directives. The power of directives can also be effectively applied when consuming data in a more unwieldy format. Consider the case in which you have a JavaScript object that exists in some sort of recursive tree structure. The view that you will generate for this object will also reflect its recursive nature and will have nested HTML elements that match the underlying data structure. (For more resources related to this topic, see here.) Recursive directives In this article by Matt Frisbie, the author of AngularJS Web Application Development Cookbook, we will see recursive directives. The power of directives can also be effectively applied when consuming data in a more unwieldy format. Consider the case in which you have a JavaScript object that exists in some sort of recursive tree structure. The view that you will generate for this object will also reflect its recursive nature and will have nested HTML elements that match the underlying data structure. Getting ready Suppose you had a recursive data object in your controller as follows: (app.js)   angular.module('myApp', []) .controller('MainCtrl', function($scope) { $scope.data = {    text: 'Primates',    items: [      {        text: 'Anthropoidea',        items: [          {            text: 'New World Anthropoids'          },          {            text: 'Old World Anthropoids',            items: [              {                text: 'Apes',                items: [                 {                    text: 'Lesser Apes'                  },                  {                    text: 'Greater Apes'                  }                ]              },              {                text: 'Monkeys'              }            ]          }        ]      },      {        text: 'Prosimii'      }    ] }; }); How to do it… As you might imagine, iteratively constructing a view or only partially using directives to accomplish this will become extremely messy very quickly. Instead, it would be better if you were able to create a directive that would seamlessly break apart the data recursively, and define and render the sub-HTML fragments cleanly. By cleverly using directives and the $compile service, this exact directive functionality is possible. The ideal directive in this scenario will be able to handle the recursive object without any additional parameters or outside assistance in parsing and rendering the object. So, in the main view, your directive will look something like this: <recursive value="nestedObject"></recursive> The directive is accepting an isolate scope = binding to the parent scope object, which will remain structurally identical as the directive descends through the recursive object. The $compile service You will need to inject the $compile service in order to make the recursive directive work. The reason for this is that each level of the directive can instantiate directives inside it and convert them from an uncompiled template to real DOM material. The angular.element() method The angular.element() method can be thought of as the jQuery $() equivalent. It accepts a string template or DOM fragment and returns a jqLite object that can be modified, inserted, or compiled for your purposes. If the jQuery library is present when the application is initialized, AngularJS will use that instead of jqLite. If you use the AngularJS template cache, retrieved templates will already exist as if you had called the angular.element() method on the template text. The $templateCache Inside a directive, it's possible to create a template using angular.element() and a string of HTML similar to an underscore.js template. However, it's completely unnecessary and quite unwieldy to use compared to AngularJS templates. When you declare a template and register it with AngularJS, it can be accessed through the injected $templateCache, which acts as a key-value store for your templates. The recursive template is as follows: <script type="text/ng-template" id="recursive.html"> <span>{{ val.text }}</span> <button ng-click="delSubtree()">delete</button> <ul ng-if="isParent" style="margin-left:30px">    <li ng-repeat="item in val.items">      <tree val="item" parent-data="val.items"></tree>    </li> </ul> </script> The <span> and <button> elements are present at each instance of a node, and they present the data at that node as well as an interface to the click event (which we will define in a moment) that will destroy it and all its children. Following these, the conditional <ul> element renders only if the isParent flag is set in the scope, and it repeats through the items array, recursing the child data and creating new instances of the directive. Here, you can see the full template definition of the directive: <tree val="item" parent-data="val.items"></tree> Not only does the directive take a val attribute for the local node data, but you can also see its parent-data attribute, which is the point of scope indirection that allows the tree structure. To make more sense of this, examine the following directive code: (app.js)   .directive('tree', function($compile, $templateCache) { return {    restrict: 'E',    scope: {      val: '=',      parentData: '='    },    link: function(scope, el, attrs) {      scope.isParent = angular.isArray(scope.val.items)      scope.delSubtree = function() {        if(scope.parentData) {            scope.parentData.splice(            scope.parentData.indexOf(scope.val),            1          );        }        scope.val={};      }        el.replaceWith(        $compile(          $templateCache.get('recursive.html')        )(scope)      );      } }; }); With all of this, if you provide the recursive directive with the data object provided at the beginning of this article, it will result in the following (presented here without the auto-added AngularJS comments and directives): (index.html – uncompiled)   <div ng-app="myApp"> <div ng-controller="MainCtrl">    <tree val="data"></tree> </div>    <script type="text/ng-template" id="recursive.html">    <span>{{ val.text }}</span>    <button ng-click="deleteSubtree()">delete</button>    <ul ng-if="isParent" style="margin-left:30px">      <li ng-repeat="item in val.items">        <tree val="item" parent-data="val.items"></tree>      </li>    </ul> </script> </div> The recursive nature of the directive templates enables nesting, and when compiled using the recursive data object located in the wrapping controller, it will compile into the following HTML: (index.html - compiled)   <div ng-controller="MainController"> <span>Primates</span> <button ng-click="delSubtree()">delete</button> <ul ng-if="isParent" style="margin-left:30px">    <li ng-repeat="item in val.items">      <span>Anthropoidea</span>      <button ng-click="delSubtree()">delete</button>      <ul ng-if="isParent" style="margin-left:30px">        <li ng-repeat="item in val.items">          <span>New World Anthropoids</span>          <button ng-click="delSubtree()">delete</button>        </li>        <li ng-repeat="item in val.items">          <span>Old World Anthropoids</span>          <button ng-click="delSubtree()">delete</button>          <ul ng-if="isParent" style="margin-left:30px">            <li ng-repeat="item in val.items">              <span>Apes</span>              <button ng-click="delSubtree()">delete</button>              <ul ng-if="isParent" style="margin-left:30px">                <li ng-repeat="item in val.items">                  <span>Lesser Apes</span>                  <button ng-click="delSubtree()">delete</button>                </li>                <li ng-repeat="item in val.items">                  <span>Greater Apes</span>                  <button ng-click="delSubtree()">delete</button>                </li>              </ul>            </li>            <li ng-repeat="item in val.items">              <span>Monkeys</span>              <button ng-click="delSubtree()">delete</button>            </li>          </ul>         </li>      </ul>    </li>    <li ng-repeat="item in val.items">      <span>Prosimii</span>      <button ng-click="delSubtree()">delete</button>    </li> </ul> </div> JSFiddle: http://jsfiddle.net/msfrisbie/ka46yx4u/ How it works… The definition of the isolate scope through the nested directives described in the previous section allows all or part of the recursive objects to be bound through parentData to the appropriate directive instance, all the while maintaining the nested connectedness afforded by the directive hierarchy. When a parent node is deleted, the lower directives are still bound to the data object and the removal propagates through cleanly. The meatiest and most important part of this directive is, of course, the link function. Here, the link function determines whether the node has any children (which simply checks for the existence of an array in the local data node) and declares the deleting method, which simply removes the relevant portion from the recursive object and cleans up the local node. Up until this point, there haven't been any recursive calls, and there shouldn't need to be. If your directive is constructed correctly, AngularJS data binding and inherent template management will take care of the template cleanup for you. This, of course, leads into the final line of the link function, which is broken up here for readability: el.replaceWith( $compile(    $templateCache.get('recursive.html') )(scope) ); Recall that in a link function, the second parameter is the jqLite-wrapped DOM object that the directive is linking—here, the <tree> element. This exposes to you a subset of jQuery object methods, including replaceWith(), which you will use here. The top-level instance of the directive will be replaced by the recursively-defined template, and this will carry down through the tree. At this point, you should have an idea of how the recursive structure is coming together. The element parameter needs to be replaced with a recursively-compiled template, and for this, you will employ the $compile service. This service accepts a template as a parameter and returns a function that you will invoke with the current scope inside the directive's link function. The template is retrieved from $templateCache by the recursive.html key, and then it's compiled. When the compiler reaches the nested <tree> directive, the recursive directive is realized all the way down through the data in the recursive object. Summary This article demonstrates the power of constructing a directive to convert a complex data object into a large DOM object. Relevant portions can be broken into individual templates, handled with distributed directive logic, and combined together in an elegant fashion to maximize modularity and reusability. Resources for Article:  Further resources on this subject: Working with Live Data and AngularJS [article] Angular Zen [article] AngularJS Project [article]
Read more
  • 0
  • 0
  • 4959

article-image-combining-vector-and-raster-datasets
Packt
22 Dec 2014
12 min read
Save for later

Combining Vector and Raster Datasets

Packt
22 Dec 2014
12 min read
This article by Michael Dorman, the author of Learning R for Geospatial Analysis, explores the interplay between vector and raster layers, and the way it is implemented in the raster package. The way rasters and vector layers can be interchanged and queried one according to the other will be demonstrated through examples. (For more resources related to this topic, see here.) Creating vector layers from a raster The opposite operation to rasterization, which has been presented in the previous section, is the creation of vector layers from raster data. The procedure of extracting features of interest out of rasters, in the form of vector layers, is often necessary for analogous reasons underlying rasterization—when the data held in a raster is better represented using a vector layer, within the context of specific subsequent analysis or visualization tasks. Scenarios where we need to create points, lines, and polygons from a raster can all be encountered. In this section, we are going to see an example of each. Raster-to-points conversion In raster-to-points conversion, each raster cell center (excluding NA cells) is converted to a point. The resulting point layer has an attribute table with the values of the respective raster cells in it. Conversion to points can be done with the rasterToPoints function. This function has a parameter named spatial that determines whether the returned object is going to be SpatialPointsDataFrame or simply a matrix holding the coordinates, and the respective cell values (spatial=FALSE, the default value). For our purposes, it is thus important to remember to specify spatial=TRUE. As an example of a raster, let's create a subset of the raster r, with only layers 1-2, rows 1-3, and columns 1-3: > u = r[[1:2]][1:3, 1:3, drop = FALSE] To make the example more instructive, we will place NA in some of the cells and see how this affects the raster-to-points conversion: > u[2, 3] = NA > u[[1]][3, 2] = NA Now, we will apply rasterToPoints to create a SpatialPointsDataFrame object named u_pnt out of u: > u_pnt = rasterToPoints(u, spatial = TRUE) Let's visually examine the result we got with the first layer of u serving as the background: > plot(u[[1]]) > plot(u_pnt, add = TRUE) The graphical output is shown in the following screenshot: We can see that a point has been produced at the center of each raster cell, except for the cell at position (2,3), where we assigned NA to both layers. However, at the (3,2) position, NA has been assigned to only one of the layers (the first one); therefore, a point feature has been generated there nevertheless. The attribute table of u_pnt has eight rows (since there are eight points) and two columns (corresponding to the raster layers). > u_pnt@data layer.1 layer.2 1 0.4242 0.4518 2 0.3995 0.3334 3 0.4190 0.3430 4 0.4495 0.4846 5 0.2925 0.3223 6 0.4998 0.5841 7     NA 0.5841 8 0.7126 0.5086 We can see that the seventh point feature, the one corresponding to the (3,2) raster position, indeed contains an NA value corresponding to layer 1. Raster-to-contours conversion Creating points (see the previous section) and polygons (see the next section) from a raster is relatively straightforward. In the former case, points are generated at cell centroids, while in the latter, rectangular polygons are drawn according to cell boundaries. On the other hand, lines can be created from a raster using various different algorithms designed for more specific purposes. Two common procedures where lines are generated based on a raster are constructing contours (lines connecting locations of equal value on the raster) and finding least-cost paths (lines going from one location to another along the easiest route when cost of passage is defined by raster values). In this section, we will see an example of how to create contours (readers interested in least-cost path calculation can refer to the gdistance package, which provides this capability in R). As an example, we will create contours from the DEM of Haifa (dem). Creating contours can be done using the rasterToContour function. This function accepts a RasterLayer object and returns a SpatialLinesDataFrame object with the contour lines. The rasterToContour function internally uses the base function contourLines, and arguments can be passed to the latter as part of the rasterToContour function call. For example, using the levels parameter, we can specify the breaks where contours will be generated (rather than letting them be determined automatically). The raster dem consists of elevation values ranging between -14 meters and 541 meters: > range(dem[], na.rm = TRUE) [1] -14 541 Therefore, we may choose to generate six contour lines, at 0, 100, 200, …, 500 meter levels: > dem_contour = rasterToContour(dem, levels = seq(0, 500, 100)) Now, we will plot the resulting SpatialLinesDataFrame object on top of the dem raster: > plot(dem) > plot(dem_contour, add = TRUE) The graphical output is shown in the following screenshot: Mount Carmel is densely covered with elevation contours compared to the plains surrounding it, which are mostly within the 0-100 meter elevation range and thus, have only few a contour lines. Let's take a look at the attribute table of dem_contour: > dem_contour@data    level C_1     0 C_2   100 C_3   200 C_4   300 C_5   400 C_6   500 Indeed, the layer consists of six line features—one for each break we specified with the levels argument. Raster-to-polygons conversion As mentioned previously, raster-to-polygons conversion involves the generation of rectangular polygons in the place of each raster cell (once again, excluding NA cells). Similar to the raster-to-points conversion, the resulting attribute table contains the respective raster values for each polygon created. The conversion to polygons is most useful with categorical rasters when we would like to generate polygons defining certain areas in order to exploit the analysis tools this type of data is associated with (such as extraction of values from other rasters, geometry editing, and overlay). Creation of polygons from a raster can be performed with a function whose name the reader may have already guessed, rasterToPolygons. A useful option in this function is to immediately dissolve the resulting polygons according to their attribute table values; that is, all polygons having the same value are dissolved into a single feature. This functionality internally utilizes the rgeos package and it can be triggered by specifying dissolve=TRUE. In our next example, we will visually compare the average NDVI time series of Lahav and Kramim forests (see earlier), based on all of our Landsat (three dates) and MODIS (280 dates) satellite images. In this article, we will only prepare the necessary data by going through the following intermediate steps: Creating the Lahav and Kramim forests polygonal layer. Extracting NDVI values from the satellite images. Creating a data.frame object that can be passed to graphical functions later. Commencing with the first step, using l_rec_focal_clump, we will first create a polygonal layer holding all NDVI>0.2 patches, then subset only those two polygons corresponding to Lahav and Kramim forests. The former is achieved using rasterToPolygons with dissolve=TRUE, converting the patches in l_rec_focal_clumpto 507 individual polygons in a new SpatialPolygonsDataFrame that we hereby name pol: > pol = rasterToPolygons(l_rec_focal_clump, dissolve = TRUE) Plotting pol will show that we have quite a few large patches and many small ones. Since the Lahav and Kramim forests are relatively large, to make things easier, we can omit all polygons with area less than or equal to 1 km2: > pol$area = gArea(pol, byid = TRUE) / 1000^2 > pol = pol[pol$area > 1, ] The attribute table shows that we are left with eight polygons, with area sizes of 1-10 km2. The clumps column, by the way, is where the original l_rec_focal_clump raster value (the clump ID) has been kept ("clumps" is the name of the l_rec_focal_clump raster layer from which the values came). > pol@data    clumps   area 112     2 1.2231 114   200 1.3284 137   221 1.9314 203   281 9.5274 240   314 6.7842 371   432 2.0007 445     5 10.2159 460     56 1.0998 Let's make a map of pol: > plotRGB(l_00, r = 3, g = 2, b = 1, stretch = "lin") > plot(pol, border = "yellow", lty = "dotted", add = TRUE) The graphical output is shown in the following screenshot: The preceding screenshot shows the continuous NDVI>0.2 patches, which are 1 km2 or larger, within the studied area. Two of these, as expected, are the forests we would like to examine. How can we select them? Obviously, we could export pol to a Shapefile and select the features of interest interactively in a GIS software (such as QGIS), then import the result back into R to continue our analysis. The raster package also offers some capabilities for interactive selection (that we do not cover here); for example, a function named click can be used to obtain the properties of the pol features we click in a graphical window such as the one shown in the preceding screenshot. However, given the purpose of this book, we will try to write a code to make the selection automatically without further user input. To write a code that makes the selection, we must choose a certain criterion (either spatial or nonspatial) that separates the features of interest. In this case, for example, we can see that the pol features we wish to select are those closest to Lahav Kibbutz. Therefore, we can utilize the towns point layer (see earlier) to find the distance of each polygon from Lahav Kibbutz, and select the two most proximate ones. Using the gDistance function, we will first find out the distances between each polygon in pol and each point in towns: > dist_towns = gDistance(towns, pol, byid = TRUE) > dist_towns              1         2 112 14524.94060 12697.151 114 5484.66695 7529.195 137 3863.12168 5308.062 203   29.48651 1119.090 240 1910.61525 6372.634 371 11687.63594 11276.683 445 12751.21123 14371.268 460 14860.25487 12300.319 The returned matrix, named dist_towns, contains the pairwise distances, with rows corresponding to the pol feature and columns corresponding to the towns feature. Since Lahav Kibbutz corresponds to the first towns feature (column "1"), we can already see that the fourth and fifth pol features (rows "203" and "240") are the most proximate ones, thus corresponding to the Lahav and Kramim forests. We could subset both forests by simply using their IDs—pol[c("203","240"),]. However, as always, we are looking for general code that will select, in this case, the two closest features irrespective of the specific IDs or row indices. For this purpose, we can use the order function, which we have not encountered so far. This function, given a numeric vector, returns the element indices in an increasing order according to element values. For example, applying order to the first column of dist_towns, we can see that the smallest element in this column is in the fourth row, the second smallest is in the fifth row, the third smallest is in the third row, and so on: > dist_order = order(dist_towns[, 1]) > dist_order [1] 4 5 3 2 6 7 1 8 We can use this result to select the relevant features of pol as follows: > forests = pol[dist_order[1:2], ] The subset SpatialPolygonsDataFrame, named forests, now contains only the two features from pol corresponding to the Lahav and Kramim forests. > forests@data    clumps   area 203   281 9.5274 240   314 6.7842 Let's visualize forests within the context of the other data we have by now. We will plot, once again, l_00 as the RGB background and pol on top of it. In addition, we will plot forests (in red) and the location of Lahav Kibbutz (as a red point). We will also add labels for each feature in pol, corresponding to its distance (in meters) from Lahav Kibbutz: > plotRGB(l_00, r = 3, g = 2, b = 1, stretch = "lin") > plot(towns[1, ], col = "red", pch = 16, add = TRUE) > plot(pol, border = "yellow", lty = "dotted", add = TRUE) > plot(forests, border = "red", lty = "dotted", add = TRUE) > text(gCentroid(pol, byid = TRUE), + round(dist_towns[,1]), + col = "White") The graphical output is shown in the following screenshot: The preceding screenshot demonstrates that we did indeed correctly select the features of interest. We can also assign the forest names to the attribute table of forests, relying on our knowledge that the first feature of forests (ID "203") is larger and more proximate to Lahav Kibbutz and corresponds to the Lahav forest, while the second feature (ID "240") corresponds to Kramim. > forests$name = c("Lahav", "Kramim") > forests@data    clumps   area   name 203   281 9.5274 Lahav 240   314 6.7842 Kramim We now have a polygonal layer named forests, with two features delineating the Lahav and Kramim forests, named accordingly in the attribute table. In the next section, we will proceed with extracting the NDVI data for these forests. Summary In this article, we closed the gap between the two main spatial data types (rasters and vector layers). We now know how to make the conversion from a vector layer to raster and vice versa, and we can transfer the geometry and data components from one data model to another when the need arises. We also saw how raster values can be extracted from a raster according to a vector layer, which is a fundamental step in many analysis tasks involving raster data. Resources for Article:  Further resources on this subject: Data visualization[article] Machine Learning in Bioinformatics[article] Specialized Machine Learning Topics[article]
Read more
  • 0
  • 0
  • 3646
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime
article-image-what-continuous-delivery-and-devops
Packt
22 Dec 2014
10 min read
Save for later

What is continuous delivery and DevOps?

Packt
22 Dec 2014
10 min read
In this article, by Paul Swartout, author of Continuous Delivery and DevOps: A Quickstart guide Second Edition, we learn that, Continuous delivery (or CD as it’s more commonly referred to) and DevOps are fast becoming the next big thing in relation to the delivery and support of software. Strictly speaking that should be the next big things as CD and DevOps are actually two complementary yet separate approaches. (For more resources related to this topic, see here.) Continuous deliver, as the name suggests, is a way of working whereby quality products, normally software assets, can be built, tested and shipped in quick succession - thus delivering value much sooner than traditional approaches. DevOps is a way of working whereby developers and IT system operators work closely, collaboratively and in harmony towards a common goal with little or no organizational barriers or boundaries between them. Let's focus on each separately: CD is a refinement on traditional agile techniques such as scrum and lean. The main refinements being the ability to decrease the time between software deliveries and the ability to do this many many times - continuously in fact. Agile delivery techniques encourage you to time-box the activities / steps needed to deliver software: definition, analysis, development, testing and sign-off but this doesn't necessarily mean that you deliver as frequently as you would like. Most agile techniques encourage you to have "potentially shippable code" at the end of your iteration / sprint. CD encourages you to ship the finished code, in small chunks, as frequently as possible. To achieve this ability one must look at the process of writing software in a slightly different way. Instead of focusing on delivery of a feature or a project in one go, you need to look at how you can deliver it in smaller, incremental chunks. This can be easier said than done. As stated above DevOps is a highly collaborative way of working whereby developers and system operators work in harmony with little or no organizational barriers between them towards a common goal. This may not sound too radical however when you consider how mainstream organizations function - especially those within government, corporate or financial sectors - and take into account the many barriers that are in place between the development and the operational teams, this can be quite a radical shift. A simple way to imagine this is to consider how a small start-up software house operates. They have a limited amount of resource and a minimum level of bureaucracy. Developing, shipping and supporting software is normally seamless - sometimes being done be the same people. Now look at a corporate business with R&D and operations teams. These two parts of the organization are normally separated by rules, regulation and red tape - in some cases they will also be separated by other part of the organization (QA teams, transition teams, etc). DevOps encourages one to break through these barriers and create an environment whereby those creating the software and those supporting the software work together in close synergy and the lines between them become blurred. DevOps is more to do with hearts and minds than anything else so can be quite tough to implement when long established bad habits are in place. Do I have to use both? You don't necessarily need to have implemented both CD and DevOps to speed up the delivery of quality software but it is true to say that both complement each other very well. If you want to continuously deliver quality software you need to have a pretty slick and streamlined process for getting the software into the production environment. This will need very close working relationships between the development teams and the operations teams. The more they work as one unit, the more seamless the process. CD will give you the tools and best of breed practices to deliver quality software quickly. DevOps will help you establish the behaviors, culture and ways of working to fully utilize CD. If you have the opportunity to implement both, it would be foolish not to at least try. Can CD and DevOps bring quantifiable business benefit? As with any business the idea is that you make more money than you spend. The quicker you can ship and sell your service / software / widget, the quicker you generate income. If you use this same model and apply it to a typical software project you may well notice that you are investing a vast amount of effort and cash simply getting your software complete and ready to ship – this is all cost. That's not the only thing that costs, releasing the software into the production environment is not free either. It can sometimes be more costly – especially if you need large QA teams, and release teams and change management teams and project coordination teams and change control boards staffed by senior managers and bug fixing teams and … I think you get the idea. In simple terms being able to develop, test and ship small changes frequently (say many times per day or week) will drastically reduce the cost of delivery and allow the business to start earning money sooner. Implementing CD and / or DevOps will help you in realizing this goal. Other business benefits include; reduction in risk of release failures, reduction in delivering redundant changes (those that are no longer needed but released anyway), increased productivity (people spend less time releasing product and more time creating products), increases competitiveness and many others. How does it fit with other product delivery methodologies? CD and DevOps should be seen as a set of tools that can be utilized to best fit your business and needs. This is nothing new, any mature agile methodology should be used in this way. As such you should be able to “bolt” elements of CD and DevOps into your existing process. For example if you currently use scrum as your product delivery methodology and have implemented CD, you could tweak your definition of done (DoD) to be “it is live” and incorporate checks against automated testing, continuous integration, and automated deployment within your acceptance criteria for each story. This therefore encourages the scrum team to work in such a way as to want to deliver their product to the live platform rather than have it sat potentially shippable waiting for someone to pick it up and release it – eventually. There may be some complexity when it comes to more traditional waterfall product delivery methodologies such as PRINCE2 or similar however there is always room for improvement – for example the requirements gathering stage could include developers and operational team members to flesh out and understand any issues around releasing and supporting the solution when it goes live. It may not seem much but these sorts of behaviors encourage closer working and collaboration. All in all CD and DevOps should be seen as complementary to your ways of working rather than a direct replacement – at least to begin with. Is it too radical for most businesses? For some organizations being able to fully implement the CD and DevOps utopia may be very complex and complicated, especially where rules, regulations and corporate governance are strongly embedded. That isn't to say that it can't be done. Even the most cumbersome and sloth like institutions can and do benefit from utilizing CD and DevOps – the UK government for example. It does need more time and effort to prove the value of implementing all / some elements of CD and DevOps and those that are proposing such a shift really need to do their homework as there may well be high degrees of resistance to overcome. However as more and more corporates, government organizations and other large business embrace CD and DevOps, the body of proof to convince the doubters becomes easier to find. Look at your business and examine the pain points in the process for delivering your goods / services / software. If you can find a specific problem (or problems) which can be solved by implementing CD or DevOps then you will have a much clearer and more convincing business case. Where do I start? As with any change, you should start small and build up – incrementally. If you see an opportunity, say a small project or some organizational change within your IT department, you should look into introducing elements of CD and DevOps. For some this may be in the form of kicking off a project to examine / build tooling to allow for automated testing, continuous integration or automated deployments. For some it may be to break down some of the barriers between development and operational teams during the development of a specific project / product. You may be able to introduce CD and / or DevOps in one big bang but just consider how things go when you try and do that with a large software release – you don't want CD and DevOps to fail so maybe big bang is not the best way. All in all it depends on your needs, the maturity of your organization and what problems you need CD and DevOps to solve. If you are convinced CD and DevOps will bring value and solve specific problems then the most important thing to do is start looking at ways to convince others. Do I just need to implement some tooling to get the benefits? This is a misconception which is normally held by those management types who have lost their connection to reality. They may have read something about CD and DevOps in some IT management periodical and decided that you just need to implement Jenkins and some automated tests. CD and DevOps do utilize tools but only where they bring value or solve a specific problem. Tooling alone will not remove ingrained business issues. Being able to build and test software quickly is good but without open, honest and collaborative ways of working to ensure the code can be shipped into production just as quickly you will end up with another problem – a backlog of software which needs to be released. Sometimes tweaking an existing process through face to face discussion is all the tooling that is needed – for example changes to the change process to allow for manual release of software as soon as it's ready. Where can I find more information? There are many forms of information available in relation to CD and DevOps one of which is a small book entitled Continuous Delivery and DevOps: A Quickstart guide Second Edition which is written by yours truly and available here (https://www.packtpub.com/application-development/continuous-delivery-and-devops-quickstart-guide-second-edition). Summary This article, like all good things, as pointed out numerous times earlier, has covered quite a lot in a few pages. This book is, by no means, the definitive opus for CD and DevOps; it is merely a collection of suggestions based on experience and observations. If you think it's not for you, you are deluded. You'll circumvent elephant-filled rivers and other unforeseen challengers as you near the journey's end. Resources for Article: Further resources on this subject: Continuous Delivery and DevOps FAQs [Article] Ansible – An Introduction [Article] Choosing the right flavor of Debian (Simple) [Article]
Read more
  • 0
  • 0
  • 10832

article-image-api-mongodb-and-nodejs
Packt
22 Dec 2014
26 min read
Save for later

API with MongoDB and Node.js

Packt
22 Dec 2014
26 min read
In this article by Fernando Monteiro, author of the book Learning Single-page Web Application Development, we will see how to build a solid foundation for our API. Our main aim is to discuss the techniques to build rich web applications, with the SPA approach. We will be covering the following topics in this article: The working of an API Boilerplates and generators The speakers API concept Creating the package.json file The Node server with server.js The model with the Mongoose schema Defining the API routes Using MongoDB in the cloud Inserting data with the Postman Chrome extension (For more resources related to this topic, see here.) The working of an API An API works through communication between different codes, thus defining specific behavior of certain objects on an interface. That is, the API will connect several functions on one website (such as search, images, news, authentications, and so on) to enable it to be used in other applications. Operating systems also have APIs, and they still have the same function. Windows, for example, has APIs such as the Win16 API, Win32 API, or Telephony API, in all its versions. When you run a program that involves some process of the operating system, it is likely that we make a connection with one or more Windows APIs. To clarify the concept of an API, we will give go through some examples of how it works. On Windows, it works on an application that uses the system clock to display the same function within the program. It then associates a behavior to a given clock time in another application, for example, using the Time/Clock API from Windows to use the clock functionality on your own application. Another example, is when you use the Android SDK to build mobile applications. When you use the device GPS, you are interacting with the API (android.location) to display the user location on the map through another API, in this case, Google Maps API. The following is the API example: When it comes to web APIs, the functionality can be even greater. There are many services that provide their code, so that they can be used on other websites. Perhaps, the best example is the Facebook API. Several other websites use this service within their pages, for instance a like button, share, or even authentication. An API is a set of programming patterns and instructions to access a software application based on the Web. So, when you access a page of a beer store in your town, you can log in with your Facebook account. This is accomplished through the API. Using it, software developers and web programmers can create beautiful programs and pages filled with content for their users. Boilerplates and generators On a MEAN stack environment, our ecosystem is infinitely diverse, and we can find excellent alternatives to start the construction of our API. At hand, we have simple boilerplates to complex code generators that can be used with other tools in an integrated way, or even alone. Boilerplates are usually a group of tested code that provides the basic structure to the main goal, that is to create a foundation of a web project. Besides saving us from common tasks such as assembling the basic structure of the code and organizing the files, boilerplates already have a number of scripts to make life easier for the frontend. Let's describe some alternatives that we consider as good starting points for the development of APIs with the Express framework, MongoDB database, Node server, and AngularJS for the frontend. Some more accentuated knowledge of JavaScript might be necessary for the complete understanding of the concepts covered here; so we will try to make them as clearly as possible. It is important to note that everything is still very new when we talk about Node and all its ecosystems, and factors such as scalability, performance, and maintenance are still major risk factors. Bearing in mind also that languages such as Ruby on Rails, Scala, and the Play framework have a higher reputation in building large and maintainable web applications, but without a doubt, Node and JavaScript will conquer your space very soon. That being said, we present some alternatives for the initial kickoff with MEAN, but remember that our main focus is on SPA and not directly on MEAN stack. Hackathon starter Hackathon is highly recommended for a quick start to develop with Node. This is because the boilerplate has the main necessary characteristics to develop applications with the Express framework to build RESTful APIs, as it has no MVC/MVVM frontend framework as a standard but just the Bootstrap UI framework. Thus, you are free to choose the framework of your choice, as you will not need to refactor it to meet your needs. Other important characteristics are the use of the latest version of the Express framework, heavy use of Jade templates and some middleware such as Passport - a Node module to manage authentication with various social network sites such as Twitter, Facebook, APIs for LinkedIn, Github, Last.fm, Foursquare, and many more. They provide the necessary boilerplate code to start your projects very fast, and as we said before, it is very simple to install; just clone the Git open source repository: git clone --depth=1 https://github.com/sahat/hackathon-starter.git myproject Run the NPM install command inside the project folder: npm install Then, start the Node server: node app.js Remember, it is very important to have your local database up and running, in this case MongoDB, otherwise the command node app.js will return the error: Error connecting to database: failed to connect to [localhost: 27017] MEAN.io or MEAN.JS This is perhaps the most popular and currently available boilerplate. MEAN.JS is a fork of the original project MEAN.io; both are open source, with a very peculiar similarity, both have the same author. You can check for more details at http://meanjs.org/. However, there are some differences. We consider MEAN.JS to be a more complete and robust environment. It has a structure of directories, better organized, subdivided modules, and better scalability by adopting a vertical modules development. To install it, follow the same steps as previously: Clone the repository to your machine: git clone https://github.com/meanjs/mean.git Go to the installation directory and type on your terminal: npm install Finally, execute the application; this time with the Grunt.js command: grunt If you are on Windows, type the following command: grunt.cmd Now, you have your app up and running on your localhost. The most common problem when we need to scale a SPA is undoubtedly the structure of directories and how we manage all of the frontend JavaScript files and HTML templates using MVC/MVVM. Later, we will see an alternative to deal with this on a large-scale application; for now, let's see the module structure adopted by MEAN.JS: Note that MEAN.JS leaves more flexibility to the AngularJS framework to deal with the MVC approach for the frontend application, as we can see inside the public folder. Also, note the modules approach; each module has its own structure, keeping some conventions for controllers, services, views, config, and tests. This is very useful for team development, so keep all the structure well organized. It is a complete solution that makes use of additional modules such as passport, swig, mongoose, karma, among others. The Passport module Some things about the Passport module must be said; it can be defined as a simple, unobtrusive authentication module. It is a powerful middleware to use with Node; it is very flexible and also modular. It can also adapt easily within applications that use the Express. It has more than 140 alternative authentications and support session persistence; it is very lightweight and extremely simple to be implemented. It provides us with all the necessary structure for authentication, redirects, and validations, and hence it is possible to use the username and password of social networks such as Facebook, Twitter, and others. The following is a simple example of how to use local authentication: var passport = require('passport'), LocalStrategy = require('passport-local').Strategy, User = require('mongoose').model('User');   module.exports = function() { // Use local strategy passport.use(new LocalStrategy({ usernameField: 'username', passwordField: 'password' }, function(username, password, done) { User.findOne({    username: username }, function(err, user) { if (err) { return done(err); } if (!user) {    return done(null, false, {    message: 'Unknown user'    }); } if (!user.authenticate(password)) {    return done(null, false, {    message: 'Invalid password'    }); } return done(null, user); }); } )); }; Here's a sample screenshot of the login page using the MEAN.JS boilerplate with the Passport module: Back to the boilerplates topic; most boilerplates and generators already have the Passport module installed and ready to be configured. Moreover, it has a code generator so that it can be used with Yeoman, which is another essential frontend tool to be added to your tool belt. Yeoman is the most popular code generator for scaffold for modern web applications; it's easy to use and it has a lot of generators such as Backbone, Angular, Karma, and Ember to mention a few. More information can be found at http://yeoman.io/. Generators Generators are for the frontend as gem is for Ruby on Rails. We can create the foundation for any type of application, using available generators. Here's a console output from a Yeoman generator: It is important to bear in mind that we can solve almost all our problems using existing generators in our community. However, if you cannot find the generator you need, you can create your own and make it available to the entire community, such as what has been done with RubyGems by the Rails community. RubyGem, or simply gem, is a library of reusable Ruby files, labeled with a name and a version (a file called gemspec). Keep in mind the Don't Repeat Yourself (DRY) concept; always try to reuse an existing block of code. Don't reinvent the wheel. One of the great advantages of using a code generator structure is that many of the generators that we have currently, have plenty of options for the installation process. With them, you can choose whether or not to use many alternatives/frameworks that usually accompany the generator. The Express generator Another good option is the Express generator, which can be found at https://github.com/expressjs/generator. In all versions up to Express Version 4, the generator was already pre-installed and served as a scaffold to begin development. However, in the current version, it was removed and now must be installed as a supplement. They provide us with the express command directly in terminal and are quite useful to start the basic settings for utilization of the framework, as we can see in the following commands: create : . create : ./package.json create : ./app.js create : ./public create : ./public/javascripts create : ./public/images create : ./public/stylesheets create : ./public/stylesheets/style.css create : ./routes create : ./routes/index.js create : ./routes/users.js create : ./views create : ./views/index.jade create : ./views/layout.jade create : ./views/error.jade create : ./bin create : ./bin/www   install dependencies:    $ cd . && npm install   run the app:    $ DEBUG=express-generator ./bin/www Very similar to the Rails scaffold, we can observe the creation of the directory and files, including the public, routes, and views folders that are the basis of any application using Express. Note the npm install command; it installs all dependencies provided with the package.json file, created as follows: { "name": "express-generator", "version": "0.0.1", "private": true, "scripts": {    "start": "node ./bin/www" }, "dependencies": {    "express": "~4.2.0",    "static-favicon": "~1.0.0",    "morgan": "~1.0.0",    "cookie-parser": "~1.0.1",    "body-parser": "~1.0.0",    "debug": "~0.7.4",    "jade": "~1.3.0" } } This has a simple and effective package.json file to build web applications with the Express framework. The speakers API concept Let's go directly to build the example API. To be more realistic, let's write a user story similar to a backlog list in agile methodologies. Let's understand what problem we need to solve by the API. The user history We need a web application to manage speakers on a conference event. The main task is to store the following speaker information on an API: Name Company Track title Description A speaker picture Schedule presentation For now, we need to add, edit, and delete speakers. It is a simple CRUD function using exclusively the API with JSON format files. Creating the package.json file Although not necessarily required at this time, we recommend that you install the Webstorm IDE, as we'll use it throughout the article. Note that we are using the Webstorm IDE with an integrated environment with terminal, Github version control, and Grunt to ease our development. However, you are absolutely free to choose your own environment. From now on, when we mention terminal, we are referring to terminal Integrated WebStorm, but you can access it directly by the chosen independent editor, terminal for Mac and Linux and Command Prompt for Windows. Webstorm is very useful when you are using a Windows environment, because Windows Command Prompt does not have the facility to copy and paste like Mac OS X on the terminal window. Initiating the JSON file Follow the steps to initiate the JSON file: Create a blank folder and name it as conference-api, open your terminal, and place the command: npm init This command will walk you through creating a package.json file with the baseline configuration for our application. Also, this file is the heart of our application; we can control all the dependencies' versions and other important things like author, Github repositories, development dependencies, type of license, testing commands, and much more. Almost all commands are questions that guide you to the final process, so when we are done, we'll have a package.json file very similar to this: { "name": "conference-api", "version": "0.0.1", "description": "Sample Conference Web Application", "main": "server.js", "scripts": {    "test": "test" }, "keywords": [    "api" ], "author": "your name here", "license": "MIT" } Now, we need to add the necessary dependencies, such as Node modules, which we will use in our process. You can do this in two ways, either directly via terminal as we did here, or by editing the package.json file. Let's see how it works on the terminal first; let's start with the Express framework. Open your terminal in the api folder and type the following command: npm install express@4.0.0 –-save This command installs the Express module, in this case, Express Version 4, and updates the package.json file and also creates dependencies automatically, as we can see: { "name": "conference-api", "version": "0.0.1", "description": "Sample Conference Web Application", "main": "server.js", "scripts": {    "test": "test" }, "keywords": [    "api" ], "author": "your name here", "license": "MIT", "dependencies": {    "express": "^4.0.0" } } Now, let's add more dependencies directly in the package.json file. Open the file in your editor and add the following lines: { "name": "conference-api", "version": "0.0.1", "description": "Sample Conference Web Application", "main": "server.js", "scripts": {    "test": "test" }, "keywords": [    "api" ], "author": "your name here", "license": "MIT", "engines": {        "node": "0.8.4",        "npm": "1.1.49" }, "dependencies": {    "body-parser": "^1.0.1",    "express": "^4.0.0",    "method-override": "^1.0.0",    "mongoose": "^3.6.13",    "morgan": "^1.0.0",    "nodemon": "^1.2.0" }, } It's very important when you deploy your application using some services such as Travis Cl or Heroku hosting company. It's always good to set up the Node environment. Open the terminal again and type the command: npm install You can actually install the dependencies in two different ways, either directly into the directory of your application or globally with the -g command. This way, you will have the modules installed to use them in any application. When using this option, make sure that you are the administrator of the user machine, as this command requires special permissions to write to the root directory of the user. At the end of the process, we'll have all Node modules that we need for this project; we just need one more action. Let's place our code over a version control, in our case Git. More information about the Git can be found at http://git-scm.com however, you can use any version control as subversion or another. We recommend using Git, as we will need it later to deploy our application in the cloud, more specificly, on Heroku cloud hosting. At this time, our project folder must have the same structure as that of the example shown here: We must point out the utilization of an important module called the Nodemon module. Whenever a file changes it restarts the server automatically; otherwise, you will have to restart the server manually every time you make a change to a file, especially in a development environment that is extremely useful, as it constantly updates our files. Node server with server.js With this structure formed, we will start the creation of the server itself, which is the creation of a main JavaScript file. The most common name used is server.js, but it is also very common to use the app.js name, especially in older versions. Let's add this file to the root folder of the project and we will start with the basic server settings. There are many ways to configure our server, and probably you'll find the best one for yourself. As we are still in the initial process, we keep only the basics. Open your editor and type in the following code: // Import the Modules installed to our server var express   = require('express'); var bodyParser = require('body-parser');   // Start the Express web framework var app       = express();   // configure app app.use(bodyParser());   // where the application will run var port     = process.env.PORT || 8080;   // Import Mongoose var mongoose   = require('mongoose');   // connect to our database // you can use your own MongoDB installation at: mongodb://127.0.0.1/databasename mongoose.connect('mongodb://username:password@kahana.mongohq.com:10073/node-api');   // Start the Node Server app.listen(port); console.log('Magic happens on port ' + port); Realize that the line-making connection with MongoDB on our localhost is commented, because we are using an instance of MongoDB in the cloud. In our case, we use MongoHQ, a MongoDB-hosting service. Later on, will see how to connect with MongoHQ. Model with the Mongoose schema Now, let's create our model, using the Mongoose schema to map our speakers on MongoDB. // Import the Mongoose module. var mongoose     = require('mongoose'); var Schema       = mongoose.Schema;   // Set the data types, properties and default values to our Schema. var SpeakerSchema   = new Schema({    name:           { type: String, default: '' },    company:       { type: String, default: '' },    title:         { type: String, default: '' },    description:   { type: String, default: '' },    picture:       { type: String, default: '' },    schedule:       { type: String, default: '' },    createdOn:     { type: Date,   default: Date.now} }); module.exports = mongoose.model('Speaker', SpeakerSchema); Note that on the first line, we added the Mongoose module using the require() function. Our schema is pretty simple; on the left-hand side, we have the property name and on the right-hand side, the data type. We also we set the default value to nothing, but if you want, you can set a different value. The next step is to save this file to our project folder. For this, let's create a new directory named server; then inside this, create another folder called models and save the file as speaker.js. At this point, our folder looks like this: The README.md file is used for Github; as we are using the Git version control, we host our files on Github. Defining the API routes One of the most important aspects of our API are routes that we take to create, read, update, and delete our speakers. Our routes are based on the HTTP verb used to access our API, as shown in the following examples: To create record, use the POST verb To read record, use the GET verb To update record, use the PUT verb To delete records, use the DELETE verb So, our routes will be as follows: Routes Verb and Action /api/speakers GET retrieves speaker's records /api/speakers/ POST inserts speakers' record /api/speakers/:speaker_id GET retrieves a single record /api/speakers/:speaker_id PUT updates a single record /api/speakers/:speaker_id DELETE deletes a single record Configuring the API routes: Let's start defining the route and a common message for all requests: var Speaker     = require('./server/models/speaker');   // Defining the Routes for our API   // Start the Router var router = express.Router();   // A simple middleware to use for all Routes and Requests router.use(function(req, res, next) { // Give some message on the console console.log('An action was performed by the server.'); // Is very important using the next() function, without this the Route stops here. next(); });   // Default message when access the API folder through the browser router.get('/', function(req, res) { // Give some Hello there message res.json({ message: 'Hello SPA, the API is working!' }); }); Now, let's add the route to insert the speakers when the HTTP verb is POST: // When accessing the speakers Routes router.route('/speakers')   // create a speaker when the method passed is POST .post(function(req, res) {   // create a new instance of the Speaker model var speaker = new Speaker();   // set the speakers properties (comes from the request) speaker.name = req.body.name; speaker.company = req.body.company; speaker.title = req.body.title; speaker.description = req.body.description; speaker.picture = req.body.picture; speaker.schedule = req.body.schedule;   // save the data received speaker.save(function(err) {    if (err)      res.send(err);   // give some success message res.json({ message: 'speaker successfully created!' }); }); }) For the HTTP GET method, we need this: // get all the speakers when a method passed is GET .get(function(req, res) { Speaker.find(function(err, speakers) {    if (err)      res.send(err);      res.json(speakers); }); }); Note that in the res.json() function, we send all the object speakers as an answer. Now, we will see the use of different routes in the following steps: To retrieve a single record, we need to pass speaker_id, as shown in our previous table, so let's build this function: // on accessing speaker Route by id router.route('/speakers/:speaker_id')   // get the speaker by id .get(function(req, res) { Speaker.findById(req.params.speaker_id, function(err,    speaker) {    if (err)      res.send(err);      res.json(speaker);    }); }) To update a specific record, we use the PUT HTTP verb and then insert the function: // update the speaker by id .put(function(req, res) { Speaker.findById(req.params.speaker_id, function(err,     speaker) {      if (err)      res.send(err);   // set the speakers properties (comes from the request) speaker.name = req.body.name; speaker.company = req.body.company; speaker.title = req.body.title; speaker.description = req.body.description; speaker.picture = req.body.picture; speaker.schedule = req.body.schedule;   // save the data received speaker.save(function(err) {    if (err)      res.send(err);      // give some success message      res.json({ message: 'speaker successfully       updated!'}); });   }); }) To delete a specific record by its id: // delete the speaker by id .delete(function(req, res) { Speaker.remove({    _id: req.params.speaker_id }, function(err, speaker) {    if (err)      res.send(err);   // give some success message res.json({ message: 'speaker successfully deleted!' }); }); }); Finally, register the Routes on our server.js file: // register the route app.use('/api', router); All necessary work to configure the basic CRUD routes has been done, and we are ready to run our server and begin creating and updating our database. Open a small parenthesis here, for a quick step-by-step process to introduce another tool to create a database using MongoDB in the cloud. There are many companies that provide this type of service but we will not go into individual merits here; you can choose your preference. We chose Compose (formerly MongoHQ) that has a free sandbox for development, which is sufficient for our examples. Using MongoDB in the cloud Today, we have many options to work with MongoDB, from in-house services to hosting companies that provide Platform as a Service (PaaS) and Software as a Service (SaaS). We will present a solution called Database as a Service (DbaaS) that provides database services for highly scalable web applications. Here's a simple step-by-step process to start using a MongoDB instance with a cloud service: Go to https://www.compose.io/. Create your free account. On your dashboard panel, click on add Database. On the right-hand side, choose Sandbox Database. Name your database as node-api. Add a user to your database. Go back to your database title, click on admin. Copy the connection string. The string connection looks like this: mongodb://<user>:<password>@kahana.mongohq.com:10073/node-api. Let's edit the server.js file using the following steps: Place your own connection string to the Mongoose.connect() function. Open your terminal and input the command: nodemon server.js Open your browser and place http://localhost:8080/api. You will see a message like this in the browser: { Hello SPA, the API is working! } Remember the api folder was defined on the server.js file when we registered the routes: app.use('/api', router); But, if you try to access http://localhost:8080/api/speakers, you must have something like this: [] This is an empty array, because we haven't input any data into MongoDB. We use an extension for the Chrome browser called JSONView. This way, we can view the formatted and readable JSON files. You can install this for free from the Chrome Web Store. Inserting data with Postman To solve our empty database and before we create our frontend interface, let's add some data with the Chrome extension Postman. By the way, it's a very useful browser interface to work with RESTful APIs. As we already know that our database is empty, our first task is to insert a record. To do so, perform the following steps: Open Postman and enter http://localhost:8080/api/speakers. Select the x-www-form-urlencoded option and add the properties of our model: var SpeakerSchema   = new Schema({ name:           { type: String, default: '' }, company:       { type: String, default: '' }, title:         { type: String, default: '' }, description:   { type: String, default: '' }, picture:       { type: String, default: '' }, schedule:       { type: String, default: '' }, createdOn:     { type: Date,   default: Date.now} }); Now, click on the blue button at the end to send the request. With everything going as expected, you should see message: speaker successfully created! at the bottom of the screen, as shown in the following screenshot: Now, let's try http://localhost:8080/api/speakers in the browser again. Now, we have a JSON file like this, instead of an empty array: { "_id": "53a38ffd2cd34a7904000007", "__v": 0, "createdOn": "2014-06-20T02:20:31.384Z", "schedule": "10:20", "picture": "fernando.jpg", "description": "Lorem ipsum dolor sit amet, consectetur     adipisicing elit, sed do eiusmod...", "title": "MongoDB", "company": "Newaeonweb", "name": "Fernando Monteiro" } When performing the same action on Postman, we see the same result, as shown in the following screenshot: Go back to Postman, copy _id from the preceding JSON file and add to the end of the http://localhost:8080/api/speakers/53a38ffd2cd34a7904000005 URL and click on Send. You will see the same object on the screen. Now, let's test the method to update the object. In this case, change the method to PUT on Postman and click on Send. The output is shown in the following screenshot: Note that on the left-hand side, we have three methods under History; now, let's perform the last operation and delete the record. This is very simple to perform; just keep the same URL, change the method on Postman to DELETE, and click on Send. Finally, we have the last method executed successfully, as shown in the following screenshot: Take a look at your terminal, you can see four messages that are the same: An action was performed by the server. We configured this message in the server.js file when we were dealing with all routes of our API. router.use(function(req, res, next) { // Give some message on the console console.log('An action was performed by the server.'); // Is very important using the next() function, without this the Route stops here. next(); }); This way, we can monitor all interactions that take place at our API. Now that we have our API properly tested and working, we can start the development of the interface that will handle all this data. Summary In this article, we have covered almost all modules of the Node ecosystem to develop the RESTful API. Resources for Article: Further resources on this subject: Web Application Testing [article] A look into responsive design frameworks [article] Top Features You Need to Know About – Responsive Web Design [article]
Read more
  • 0
  • 0
  • 4259

article-image-pipeline-and-producer-consumer-design-patterns
Packt
20 Dec 2014
48 min read
Save for later

Pipeline and Producer-consumer Design Patterns

Packt
20 Dec 2014
48 min read
In this article created by Rodney Ringler, the author of C# Multithreaded and Parallel Programming, we will explore two popular design patterns to solve concurrent problems—Pipeline and producer-consumer, which are used in developing parallel applications using the TPL. A Pipeline design is one where an application is designed with multiple tasks or stages of functionality with queues of work items between them. So, for each stage, the application will read from a queue of work to be performed, execute the work on that item, and then queue the results for the next stage. By designing the application this way, all of the stages can execute in parallel. Each stage just reads from its work queue, performs the work, and puts the results of the work into the queue for the next stage. Each stage is a task and can run independently of the other stages or tasks. They continue executing until their queue is empty and marked completed. They also block and wait for more work items if the queue is empty but not completed. The producer-consumer design pattern is a similar concept but different. In this design, we have a set of functionality that produces data that is then consumed by another set of functionality. Each set of functionality is a TPL task. So, we have a producer task and a consumer task, with a buffer between them. Each of these tasks can run independently of each other. We can also have multiple producer tasks and multiple consumer tasks. The producers run independently and produce queue results to the buffer. The consumers run independently and dequeue from the buffer and perform work on the item. The producer can block if the buffer is full and wait for room to become available before producing more results. Also, the consumer can block if the buffer is empty, waiting on more results to be available to consume. In this article, you will learn the following: Designing an application with a Pipeline design Designing an application with a producer-consumer design Learning how to use BlockingCollection Learning how to use BufferedBlocks Understanding the classes of the System.Threading.Tasks.Dataflow library (For more resources related to this topic, see here.) Pipeline design pattern The Pipeline design is very useful in parallel design when you can divide an application up into series of tasks to be performed in such a way that each task can run concurrently with other tasks. It is important that the output of each task is in the same order as the input. If the order does not matter, then a parallel loop can be performed. When the order matters and we don't want to wait until all items have completed task A before the items start executing task B, then a Pipeline implementation is perfect. Some applications that lend themselves to pipelining are video streaming, compression, and encryption. In each of these examples, we need to perform a set of tasks on the data and preserve the data's order, but we do not want to wait for each item of data to perform a task before any of the data can perform the next task. The key class that .NET has provided for implementing this design pattern is BlockingCollection of the System.Collections.Concurrent namespace. The BlockingCollection class was introduced with .NET 4.5. It is a thread-safe collection specifically designed for producer-consumer and Pipeline design patterns. It supports concurrently adding and removing items by multiple threads to and from the collection. It also has methods to add and remove that block when the collection is full or empty. You can specify a maximum collection size to ensure a producing task that outpaces a consuming task does not make the queue too large. It supports cancellation tokens. Finally, it supports enumerations so that you can use the foreach loop when processing items of the collection. A producer of items to the collection can call the CompleteAdding method when the last item of data has been added to the collection. Until this method is called if a consumer is consuming items from the collection with a foreach loop and the collection is empty, it will block until an item is put into the collection instead of ending the loop. Next, we will see a simple example of a Pipeline design implementation using an encryption program. This program will implement three stages in our pipeline. The first stage will read a text file character-by-character and place each character into a buffer (BlockingCollection). The next stage will read each character out of the buffer and encrypt it by adding 1 to its ASCII number. It will then place the new character into our second buffer and write it to an encryption file. Our final stage will read the character out of the second buffer, decrypt it to its original character, and write it out to a new file and to the screen. As you will see, stages 2 and 3 will start processing characters before stage 1 has finished reading all the characters from the input file. And all of this will be done while maintaining the order of the characters so that the final output file is identical to the input file: Let's get started. How to do it First, let's open up Visual Studio and create a new Windows Presentation Foundation (WPF) application named PipeLineApplication and perform the following steps: Create a new class called Stages.cs. Next, make sure it has the following using statements. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; In the MainWindow.xaml.cs file, make sure the following using statements are present: using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Threading; Next, we will add a method for each of the three stages in our pipeline. First, we will create a method called FirstStage. It will take two parameters: one will be a BlockingCollection object that will be the output buffer of this stage, and the second will be a string pointing to the input data file. This will be a text file containing a couple of paragraphs of text to be encrypted. We will place this text file in the projects folder on C:. The FirstStage method will have the following code: public void FirstStage(BlockingCollection<char> output, String PipelineInputFile)        {            String DisplayData = "";            try            {                foreach (char C in GetData(PipelineInputFile))                { //Displayed characters read in from the file.                   DisplayData = DisplayData + C.ToString();   // Add each character to the buffer for the next stage.                    output.Add(C);                  }            }            finally            {                output.CompleteAdding();             }      } Next, we will add a method for the second stage called StageWorker. This method will not return any values and will take three parameters. One will be a BlockingCollection value that will be its input buffer, the second one will be the output buffer of the stage, and the final one will be a file path to store the encrypted text in a data file. The code for this method will look like this: public void StageWorker(BlockingCollection<char> input, BlockingCollection<char> output, String PipelineEncryptFile)        {            String DisplayData = "";              try            {                foreach (char C in input.GetConsumingEnumerable())                {                    //Encrypt each character.                    char encrypted = Encrypt(C);                      DisplayData = DisplayData + encrypted.ToString();   //Add characters to the buffer for the next stage.                    output.Add(encrypted);                  }   //write the encrypted string to the output file.                 using (StreamWriter outfile =                            new StreamWriter(PipelineEncryptFile))                {                    outfile.Write(DisplayData);                }              }            finally            {                output.CompleteAdding();            }        } Now, we will add a method for the third and final stage of the Pipeline design. This method will be named FinalStage. It will not return any values and will take two parameters. One will be a BlockingCollection object that is the input buffer and the other will be a string pointing to an output data file. It will have the following code in it: public void FinalStage(BlockingCollection<char> input, String PipelineResultsFile)        {            String OutputString = "";            String DisplayData = "";              //Read the encrypted characters from the buffer, decrypt them, and display them.            foreach (char C in input.GetConsumingEnumerable())            {                //Decrypt the data.                char decrypted = Decrypt(C);                  //Display the decrypted data.                DisplayData = DisplayData + decrypted.ToString();                  //Add to the output string.                OutputString += decrypted.ToString();              }              //write the decrypted string to the output file.            using (StreamWriter outfile =                        new StreamWriter(PipelineResultsFile))            {                outfile.Write(OutputString);            }        } Now that we have methods for the three stages of our pipeline, let's add a few utility methods. The first of these methods will be one that reads in the input data file and places each character in the data file in a List object. This method will take a string parameter that has a filename and will return a List object of characters. It will have the following code: public List<char> GetData(String PipelineInputFile)        {            List<char> Data = new List<char>();              //Get the Source data.            using (StreamReader inputfile = new StreamReader(PipelineInputFile))            {                while (inputfile.Peek() >= 0)                {                    Data.Add((char)inputfile.Read());                }              }              return Data;        } Now we will need a method to encrypt the characters. This will be a simple encryption method. The encryption method is not really important to this exercise. This exercise is designed to demonstrate the Pipeline design, not implement the world's toughest encryption. This encryption will simply take each character and add one to its ASCII numerical value. The method will take a character type as an input parameter and return a character. The code for it will be as follows: public char Encrypt(char C)        {            //Take the character, convert to an int, add 1, then convert back to a character.            int i = (int)C;            i = i + 1;            C = Convert.ToChar(i);              return C; } Now we will add one final method to the Stages class to decrypt a character value. It will simply do the reverse of the encrypt method. It will take the ASCII numerical value and subtract 1. The code for this method will look like this: public char Decrypt(char C)      {            int i = (int)C;            i = i - 1;            C = Convert.ToChar(i);              return C;        } Now that we are done with the Stages class, let's switch our focus back to the MainWindow.xaml.cs file. First, you will need to add three using statements. They are for the StreamReader, StreamWriter, Threads, and BlockingCollection classes: using System.Collections.Concurrent; using System.IO; using System.Threading; At the top of the MainWindow class, we need four variables available for the whole class. We need three strings that point to our three data files—the input data, encrypted data, and output data. Then we will need a Stages object. These declarations will look like this: private static String PipelineResultsFile = @"c:projectsOutputData.txt";        private static String PipelineEncryptFile = @"c:projectsEncryptData.txt";        private static String PipelineInputFile = @"c:projectsInputData.txt";        private Stages Stage; Then, in the MainWindow constructor method, right after the InitializeComponent call, add a line to instantiate our Stages object: //Create the Stage object and register the event listeners to update the UI as the stages work. Stage = new Stages(); Next, add a button to the MainWindow.xaml file that will initiate the pipeline and encryption. Name this button control butEncrypt, and set its Content property to Encrypt File. Next, add a click event handler for this button in the MainWindow.xaml.cs file. Its event handler method will be butEncrypt_Click and will contain the main code for this application. It will instantiate two BlockingCollection objects for two queues. One queue between stages 1 and 2, and one queue between stages 2 and 3. This method will then create a task for each stage that executes the corresponding methods from the Stages classes. It will then start these three tasks and wait for them to complete. Finally, it will write the output of each stage to the input, encrypted, and results data files and text blocks for viewing. The code for it will look like the following code: private void butEncrpt_Click(object sender, RoutedEventArgs e)        {            //PipeLine Design Pattern              //Create queues for input and output to stages.            int size = 20;            BlockingCollection<char> Buffer1 = new BlockingCollection<char>(size);            BlockingCollection<char> Buffer2 = new BlockingCollection<char>(size);              TaskFactory tasks = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);              Task Stage1 = tasks.StartNew(() => Stage.FirstStage(Buffer1, PipelineInputFile));            Task Stage2 = tasks.StartNew(() => Stage.StageWorker(Buffer1, Buffer2, PipelineEncryptFile));            Task Stage3 = tasks.StartNew(() => Stage.FinalStage(Buffer2, PipelineResultsFile));              Task.WaitAll(Stage1, Stage2, Stage3);              //Display the 3 files.            using (StreamReader inputfile = new StreamReader(PipelineInputFile))            {                while (inputfile.Peek() >= 0)                {                    tbStage1.Text = tbStage1.Text + (char)inputfile.Read();                }              }            using (StreamReader inputfile = new StreamReader(PipelineEncryptFile))            {                 while (inputfile.Peek() >= 0)                {                    tbStage2.Text = tbStage2.Text + (char)inputfile.Read();                }              }            using (StreamReader inputfile = new StreamReader(PipelineResultsFile))             {                while (inputfile.Peek() >= 0)                {                    tbStage3.Text = tbStage3.Text + (char)inputfile.Read();                }              }      } One last thing. Let's add three textblocks to display the outputs. We will call these tbStage1, tbStage2, and tbStage3. We will also add three label controls with the text Input File, Encrypted File, and Output File. These will be placed by the corresponding textblocks. Now, the MainWindow.xaml file should look like the following screenshot: Now we will need an input data file to encrypt. We will call this file InputData.txt and put it in the C:projects folder on our computer. For our example, we have added the following text to it: We are all finished and ready to try it out. Compile and run the application and you should have a window that looks like the following screenshot: Now, click on the Encrypt File button and you should see the following output: As you can see, the input and output files look the same and the encrypted file looks different. Remember that Input File is the text we put in the input data text file; this is the input from the end of stage 1 after we have read the file in to a character list. Encrypted File is the output from stage 2 after we have encrypted each character. Output File is the output of stage 3 after we have decrypted the characters again. It should match Input File. Now, let's take a look at how this works. How it works Let's look at the butEncrypt click event handler method in the MainWindow.xaml.cs file, as this is where a lot of the action takes place. Let's examine the following lines of code:            //Create queues for input and output to stages.            int size = 20;            BlockingCollection<char> Buffer1 = new BlockingCollection<char>(size);            BlockingCollection<char> Buffer2 = new BlockingCollection<char>(size);            TaskFactory tasks = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);              Task Stage1 = tasks.StartNew(() => Stage.FirstStage(Buffer1, PipelineInputFile));            Task Stage2 = tasks.StartNew(() => Stage.StageWorker(Buffer1, Buffer2, PipelineEncryptFile));            Task Stage3 = tasks.StartNew(() => Stage.FinalStage(Buffer2, PipelineResultsFile)); First, we create two queues that are implemented using BlockingCollection objects. Each of these is set with a size of 20 items. These two queues take a character datatype. Then we create a TaskFactory object and use it to start three tasks. Each task uses a lambda expression that executes one of the stages methods from the Stages class—FirstStage, StageWorker, and FinalStage. So, now we have three separate tasks running besides the main UI thread. Stage1 will read the input data file character by character and place each character in the queue Buffer1. Remember that this queue can only hold 20 items before it will block the FirstStage method waiting on room in the queue. This is how we know that Stage2 starts running before Stage1 completes. Otherwise, Stage1 will only queue the first 20 characters and then block. Once Stage1 has read all of the characters from the input file and placed them into Buffer1, it then makes the following call:            finally            {                output.CompleteAdding();            } This lets the BlockingCollection instance, Buffer1, to know that there are no more items to be put in the queue. So, when Stage2 has emptied the queue after Stage1 has called this method, it will not block but will instead continue until completion. Prior to the CompleteAdding method call, Stage2 will block if Buffer1 is empty, waiting until more items are placed in the queue. This is why a BlockingCollection instance was developed for Pipeline and producer-consumer applications. It provides the perfect mechanism for this functionality. When we created the TaskFactory, we used the following parameter: TaskCreationOptions.LongRunning This tells the threadpool that these tasks may run for a long time and could occasionally block waiting on their queues. In this way, the threadpool can decide how to best manage the threads allocated for these tasks. Now, let's look at the code in Stage2—the StageWorker method. We need a way to remove items in an enumerable way so that we can iterate over the queues items with a foreach loop because we do not know how many items to expect. Also, since BlockingCollection objects support multiple consumers, we need a way to remove items that no other consumer might remove. We use this method of the BlockingCollection class: foreach (char C in input.GetConsumingEnumerable()) This allows multiple consumers to remove items from a BlockingCollection instance while maintaining the order of the items. To further improve performance of this application (assuming we have enough available processing cores), we could create a fourth task that also runs the StageWorker method. So, then we would have two stages and two tasks running. This might be helpful if there are enough processing cores and stage 1 runs faster than stage 2. If this happens, it will continually fill the queue and block until space becomes available. But if we run multiple stage 2 tasks, then we will be able to keep up with stage 1. Then, finally we have this line of code: Task.WaitAll(Stage1, Stage2, Stage3); This tells our button handler to wait until all of the tasks are complete. Once we have called the CompleteAdding method on each BlockingCollection instance and the buffers are then emptied, all of our stages will complete and the TaskFactory.WaitAll command will be satisfied and this method on the UI thread can complete its processing, which in this application is to update the UI and data files:            //Display the 3 files.            using (StreamReader inputfile = new StreamReader(PipelineInputFile))            {                while (inputfile.Peek() >= 0)                {                    tbStage1.Text = tbStage1.Text + (char)inputfile.Read();                }              }            using (StreamReader inputfile = new StreamReader(PipelineEncryptFile))            {                while (inputfile.Peek() >= 0)                {                    tbStage2.Text = tbStage2.Text + (char)inputfile.Read();                }              }            using (StreamReader inputfile = new StreamReader(PipelineResultsFile))            {                while (inputfile.Peek() >= 0)                {                    tbStage3.Text = tbStage3.Text + (char)inputfile.Read();                }              } Next, experiment with longer running, more complex stages and multiple consumer stages. Also, try stepping through the application with the Visual Studio debugger. Make sure you understand the interaction between the stages and the buffers. Explaining message blocks Let's talk for a minute about message blocks and the TPL. There is a new library that Microsoft has developed as part of the TPL, but it does not ship directly with .NET 4.5. This library is called the TPL Dataflow library. It is located in the System.Threading.Tasks.Dataflow namespace. It comes with various dataflow components that assist in asynchronous concurrent applications where messages need to be passed between multiple tasks or the data needs to be passed when it becomes available, as in the case of a web camera streaming video. The Dataflow library's message blocks are very helpful for design patterns such as Pipeline and producer-consumer where you have multiple producers producing data that can be consumed by multiple consumers. The two that we will take a look at are BufferBlock and ActionBlock. The TPL Dataflow library contains classes to assist in message passing and parallelizing I/O-heavy applications that have a lot of throughput. It provides explicit control over how data is buffered and passed. Consider an application that asynchronously loads large binary files from storage and manipulates that data. Traditional programming requires that you use callbacks and synchronization classes, such as locks, to coordinate tasks and have access to data that is shared. By using the TPL Dataflow objects, you can create objects that process image files as they are read in from a disk location. You can set how data is handled when it becomes available. Because the CLR runtime engine manages dependencies between data, you do not have to worry about synchronizing access to shared data. Also, since the CLR engine schedules the work depending on the asynchronous arrival of data, the TPL Dataflow objects can improve performance by managing the threads the tasks run on. In this section, we will cover two of these classes, BufferBlock and ActionBlock. The TPL Dataflow library (System.Threading.Tasks.Dataflow) does not ship with .NET 4.5. To install System.Threading.Tasks.Dataflow, open your project in Visual Studio, select Manage NuGet Packages from under the Project menu and then search online for Microsoft.Tpl.Dataflow. BufferBlock The BufferBlock object in the Dataflow library provides a buffer to store data. The syntax is, BufferBlock<T>. The T indicates that the datatype is generic and can be of any type. All static variables of this object type are guaranteed to be thread-safe. BufferBlock is an asynchronous message structure that stores messages in a first-in-first-out queue. Messages can be "posted" to the queue by multiple producers and "received" from the queue by multiple consumers. The TPL DatafLow library provides interfaces for three types of objects—source blocks, target blocks, and propagator blocks. BufferBlock is a general-purpose message block that can act as both a source and a target message buffer, which makes it perfect for a producer-consumer application design. To act as both a source and a target, it implements two interfaces defined by the TPL Dataflow library—ISourceBlock<TOutput> and ITargetBlock<TOutput>. So, in the application that we will develop in the Producer-consumer design pattern section of this article, you will see that the producer method implements BufferBlock using the ITargetBlock interface and the consumer implements BufferBlock with the ISourceBlock interface. This will be the same BufferBlock object that they will act on but by defining their local objects with a different interface there will be different methods available to use. The producer method will have Post and Complete methods, and the consumer method will use the OutputAvailableAsync and Receive methods. The BufferBlock object only has two properties, namely Count, which is a count of the number of data messages in the queue, and Completion, which gets a task that is an asynchronous operation and completion of the message block. The following is a set of methods for this class: Referenced from http://msdn.microsoft.com/en-us/library/hh160414(v=vs.110).aspx Here is a list of the extension methods provided by the interfaces that it implements: Referenced from http://msdn.microsoft.com/en-us/library/hh160414(v=vs.110).aspx Finally, here are the interface references for this class: Referenced from http://msdn.microsoft.com/en-us/library/hh160414(v=vs.110).aspx So, as you can see, these interfaces make using the BufferBlock object as a general-purpose queue between stages of a pipeline very easy. This technique is also useful between producers and consumers in a producer-consumer design pattern. ActionBlock Another very useful object in the Dataflow library is ActionBlock. Its syntax is ActionBlock<TInput>, where TInput is an Action object. ActionBlock is a target block that executes a delegate when a message of data is received. The following is a very simple example of using an ActionBlock:            ActionBlock<int> action = new ActionBlock<int>(x => Console.WriteLine(x));              action.Post(10); In this sample piece of code, the ActionBlock object is created with an integer parameter and executes a simple lambda expression that does a Console.WriteLine when a message of data is posted to the buffer. So, when the action.Post(10) command is executed, the integer, 10, is posted to the ActionBlock buffer and then the ActionBlock delegate, implemented as a lambda expression in this case, is executed. In this example, since this is a target block, we would then need to call the Complete method to ensure the message block is completed. Another handy method of the BufferBlock is the LinkTo method. This method allows you to link ISourceBlock to ITargetBlock. So, you can have a BufferBlock that is implemented as an ISourceBlock and link it to an ActionBlock since it is an ITargetBlock. In this way, an Action delegate can be executed when a BufferBlock receives data. This does not dequeue the data from the message block. It just allows you to execute some task when data is received into the buffer. ActionBlock only has two properties, namely InputCount, which is a count of the number of data messages in the queue, and Completion, which gets a task that is an asynchronous operation and completion of the message block. It has the following methods: Referenced from http://msdn.microsoft.com/en-us/library/hh194684(v=vs.110).aspx The following extension methods are implemented from its interfaces: Referenced from http://msdn.microsoft.com/en-us/library/hh194684(v=vs.110).aspx Also, it implements the following interfaces: Referenced from http://msdn.microsoft.com/en-us/library/hh194684(v=vs.110).aspx Now that we have examined a little of the Dataflow library that Microsoft has developed, let's use it in a producer-consumer application. Producer-consumer design pattern Now, that we have covered the TPL's Dataflow library and the set of objects it provides to assist in asynchronous message passing between concurrent tasks, let's take a look at the producer-consumer design pattern. In a typical producer-consumer design, we have one or more producers putting data into a queue or message data block. Then we have one or more consumers taking data from the queue and processing it. This allows for asynchronous processing of data. Using the Dataflow library objects, we can create a consumer task that monitors a BufferBlock and pulls items of the data from it when they arrive. If no items are available, the consumer method will block until items are available or the BufferBlock has been set to Complete. Because of this, we can start our consumer at any time, even before the producer starts to put items into the queue. Then we create one or more tasks that produce items and place them into the BufferBlock. Once the producers are finished processing all items of data to the BufferBlock, they can mark the block as Complete. Until then, the BufferBlock object is still available to add items into. This is perfect for long-running tasks and applications when we do not know when the data will arrive. Because the producer task is implementing an input parameter of a BufferBlock as an ITargetBlock object and the consumer task is implementing an input parameter of a BufferBlock as an ISourceBlock, they can both use the same BufferBlock object but have different methods available to them. One has methods to produces items to the block and mark it complete. The other one has methods to receive items and wait for more items until the block is marked complete. In this way, the Dataflow library implements the perfect object to act as a queue between our producers and consumers. Now, let's take a look at the application we developed previously as a Pipeline design and modify it using the Dataflow library. We will also remove a stage so that it just has two stages, one producer and one consumer. How to do it The first thing we need to do is open Visual Studio and create a new console application called ProducerConsumerConsoleApp. We will use a console application this time just for ease. Our main purpose here is to demonstrate how to implement the producer-consumer design pattern using the TPL Dataflow library. Once you have opened Visual Studio and created the project, we need to perform the following steps: First, we need to install and add a reference to the TPL Dataflow library. The TPL Dataflow library (System.Threading.Tasks.Dataflow) does not ship with .NET 4.5. Select Manage NuGet Packages from under the Project menu and then search online for Microsoft.Tpl.Dataflow. Now, we will need to add two using statements to our program. One for StreamReader and StreamWriter and one for the BufferBlock object: using System.Threading.Tasks.Dataflow; using System.IO; Now, let's add two static strings that will point to our input data file and the encrypted data file that we output: private static String PipelineEncryptFile = @"c:projectsEncryptData.txt";        private static String PipelineInputFile = @"c:projectsInputData.txt"; Next, let's add a static method that will act as our producer. This method will have the following code:        // Our Producer method.        static void Producer(ITargetBlock<char> Target)        {            String DisplayData = "";              try            {                foreach (char C in GetData(PipelineInputFile))                {                      //Displayed characters read in from the file.                    DisplayData = DisplayData + C.ToString();                      // Add each character to the buffer for the next stage.                    Target.Post(C);                  }            }              finally            {                Target.Complete();            }          } Then we will add a static method to perform our consumer functionality. It will have the following code:        // This is our consumer method. IT runs asynchronously.        static async Task<int> Consumer(ISourceBlock<char> Source)        {            String DisplayData = "";              // Read from the source buffer until the source buffer has no            // available output data.            while (await Source.OutputAvailableAsync())            {                    char C = Source.Receive();                      //Encrypt each character.                    char encrypted = Encrypt(C);                      DisplayData = DisplayData + encrypted.ToString();              }              //write the decrypted string to the output file.            using (StreamWriter outfile =                         new StreamWriter(PipelineEncryptFile))            {                outfile.Write(DisplayData);            }              return DisplayData.Length;        } Then, let's create a simple static helper method to read our input data file and put it in a List collection character by character. This will give us a character list for our producer to use. The code in this method will look like this:        public static List<char> GetData(String PipelineInputFile)        {            List<char> Data = new List<char>();              //Get the Source data.            using (StreamReader inputfile = new StreamReader(PipelineInputFile))            {                while (inputfile.Peek() >= 0)                {                    Data.Add((char)inputfile.Read());                }              }              return Data;        } Next, we will add a static method to encrypt our characters. This method will work like the one we used in our pipelining application. It will add one to the ASCII numerical value of the character:        public static char Encrypt(char C)        {            //Take the character, convert to an int, add 1, then convert back to a character.            int i = (int)C;            i = i + 1;            C = Convert.ToChar(i);              return C;        } Then, we need to add the code for our Then, we need to add the code for our Main method. This method will start our consumer and producer tasks. Then, when they have completed processing, it will display the results in the console. The code for this method looks like this:        static void Main(string[] args)        {            // Create the buffer block object to use between the producer and consumer.            BufferBlock<char> buffer = new BufferBlock<char>();              // The consumer method runs asynchronously. Start it now.            Task<int> consumer = Consumer(buffer);              // Post source data to the dataflow block.            Producer(buffer);              // Wait for the consumer to process all data.            consumer.Wait();              // Print the count of characters from the input file.            Console.WriteLine("Processed {0} bytes from input file.", consumer.Result);              //Print out the input file to the console.            Console.WriteLine("rnrn");            Console.WriteLine("This is the input data file. rn");            using (StreamReader inputfile = new StreamReader(PipelineInputFile))            {                while (inputfile.Peek() >= 0)                {                    Console.Write((char)inputfile.Read());                }              }              //Print out the encrypted file to the console.            Console.WriteLine("rnrn");            Console.WriteLine("This is the encrypted data file. rn");            using (StreamReader encryptfile = new StreamReader(PipelineEncryptFile))            {                while (encryptfile.Peek() >= 0)                {                    Console.Write((char)encryptfile.Read());                }              }             //Wait before closing the application so we can see the results.            Console.ReadLine();        } That is all the code that is needed. Now, let's build and run the application using the following input data file: Once it runs and completes, your output should look like the following screenshot: Now, try this with your own data files and inputs. Let's examine what happened and how this works. How it works First we will go through the Main method. The first thing Main does is create a BufferBlock object called buffer. This will be used as the queue of items between our producer and consumer. This BufferBlock is defined to accept character datatypes. Next, we start our consumer task using this command: Task<int> consumer = Consumer(buffer); Also, note that when this buffer object goes into the consumer task, it is cast as ISourceBlock. Notice the method header of our consumer: static async Task<int> Consumer(ISourceBlock<char> Source) Next, our Main method starts our producer task using the following command: Producer(buffer); Then we wait until our consumer task finishes, using this command: consumer.Wait(); So, now our Main method just waits. Its work is done for now. It has started both the producer and consumer tasks. Now our consumer is waiting for items to appear in its BufferBlock so it can process them. The consumer will stay in the following loop until all items are removed from the message block and the block has been completed, which is done by someone calling its Complete method:      while (await Source.OutputAvailableAsync())            {                    char C = Source.Receive();                      //Encrypt each character.                    char encrypted = Encrypt(C);                      DisplayData = DisplayData + encrypted.ToString();              } So, now our consumer task will loop asynchronously, removing items from the message queue as they appear. It uses the following command in the while loop to do this: await Source.OutputAvailableAsync()) Likewise, other consumer tasks can run at the same time and do the same thing. If the producer is adding items to the block quicker than the consumer can process them, then adding another consumer will improve performance. Once an item is available, then the consumer calls the following command to get the item from the buffer: char C = Source.Receive(); Since the buffer contains items of type character, we place the item received into a character value. Then the consumer processes it by encrypting the character and appending it to our display string: Now, let's look at the consumer. The consumer first gets its data by calling the following command: GetData(PipelineInputFile) This method returns a List collection of characters that has an item for each character in the input data file. Now the producer iterates through the collection and uses the following command to place each item into the buffer block: Target.Post(C); Also, notice in the method header for our consumer that we cast our buffer as an ITargetBlock type: static void Producer(ITargetBlock<char> Target) Once the producer is done processing characters and adding them to the buffer, it officially closes the BufferBlock object using this command: Target.Complete(); That is it for the producer and consumer. Once the Main method is done waiting on the consumer to finish, it then uses the following code to write out the number of characters processed, the input data, and the encrypted data:      // Print the count of characters from the input file.            Console.WriteLine("Processed {0} bytes from input file.", consumer.Result);              //Print out the input file to the console.            Console.WriteLine("rnrn");            Console.WriteLine("This is the input data file. rn");            using (StreamReader inputfile = new StreamReader(PipelineInputFile))            {                while (inputfile.Peek() >= 0)                {                    Console.Write((char)inputfile.Read());                }              }              //Print out the encrypted file to the console.            Console.WriteLine("rnrn");            Console.WriteLine("This is the encrypted data file. rn");            using (StreamReader encryptfile = new StreamReader(PipelineEncryptFile))            {                while (encryptfile.Peek() >= 0)                {                    Console.Write((char)encryptfile.Read());                }              } Now that you are comfortable implementing a basic producer-consumer design using objects from the TPL Dataflow library, try experimenting with this basic idea but use multiple producers and multiple consumers all with the same BufferBlock object as the queue between them all. Also, try converting our original Pipeline application from the beginning of the article into a TPL Dataflow producer-consumer application with two sets of producers and consumers. The first will act as stage 1 and stage 2, and the second will act as stage 2 and stage 3. So, in effect, stage 2 will be both a consumer and a producer. Summary We have covered a lot in this article. We have learned the benefits and how to implement a Pipeline design pattern and a producer-consumer design pattern. As we saw, these are both very helpful design patterns when building parallel and concurrent applications that require multiple asynchronous processes of data between tasks. In the Pipeline design, we are able to run multiple tasks or stages concurrently even though the stages rely on data being processed and output by other stages. This is very helpful for performance since all functionality doesn't have to wait on each stage to finish processing every item of data. In our example, we are able to start decrypting characters of data while a previous stage is still encrypting data and placing it into the queue. In the Pipeline example, we examined the benefits of the BlockingCollection class in acting as a queue between stages in our pipeline. Next, we explored the new TPL Dataflow library and some of its message block classes. These classes implement several interfaces defined in the library—ISourceBlock, ITargetBlock, and IPropogatorBlock. By implementing these interfaces, it allows us to write generic producer and consumer task functionality that can be reused in a variety of applications. Both of these design patterns and the Dataflow library allow for easy implementations of common functionality in a concurrent manner. You will use these techniques in many applications, and this will become a go-to design pattern when you evaluate a system's requirements and determine how to implement concurrency to help improve performance. Like all programming, parallel programming is made easier when you have a toolbox of easy-to-use techniques that you are comfortable with. Most applications that benefit from parallelism will be conducive to some variation of a producer-consumer or Pipeline pattern. Also, the BlockingCollection and Dataflow message block objects are useful mechanisms for coordinating data between parallel tasks, no matter what design pattern is used in the application. It will be very useful to become comfortable with these messaging and queuing classes. Resources for Article: Further resources on this subject: Parallel Programming Patterns [article] Watching Multiple Threads in C# [article] Clusters, Parallel Computing, and Raspberry Pi – A Brief Background [article]
Read more
  • 0
  • 0
  • 11302

article-image-evolving-data-model
Packt
19 Dec 2014
11 min read
Save for later

Evolving the data model

Packt
19 Dec 2014
11 min read
In this article by C. Y. Kan, author of the book Cassandra Data Modeling and Analysis, we will see the techniques of how to evolve an existing Cassandra data model in detail. Meanwhile, the techniques of modeling by query will be demonstrated as well. (For more resources related to this topic, see here.) The Stock Screener Application is good enough to retrieve and analyze a single stock at one time. However, scanning just a single stock looks very limited in practical use. A slight improvement can be made here; it can handle a bunch of stocks instead of one. This bunch of stocks will be stored as Watch List in the Cassandra database. Accordingly, the Stock Screener Application will be modified to analyze the stocks in the Watch List, and therefore it will produce alerts for each of the stocks being watched based on the same screening rule. For the produced alerts, saving them in Cassandra will be beneficial for backtesting trading strategies and continuous improvement of the Stock Screener Application. They can be reviewed from time to time without having to review them on the fly. Backtesting is a jargon used to refer to testing a trading strategy, investment strategy, or a predictive model using existing historical data. It is also a special type of cross-validation applied to time series data. In addition, when the number of the stocks in the Watch List grows to a few hundred, it will be difficult for a user of the Stock Screener Application to recall what the stocks are by simply referring to their stock codes. Hence, it would be nice to have the name of the stocks added to the produced alerts to make them more descriptive and user-friendly. Finally, we might have an interest in finding out how many alerts were generated on a particular stock over a specified period of time and how many alerts were generated on a particular date. We will use CQL to write queries to answer these two questions. By doing so, the modeling by query technique can be demonstrated. The enhancement approach The enhancement approach consists of four change requests in total. First, we will conduct changes in the data model and then the code will be enhanced to provide the new features. Afterwards, we will test run the enhanced Stock Screener Application again. The parts of the Stock Screener Application that require modifications are highlighted in the following figure. It is remarkable that two new components are added to the Stock Screener Application. The first component, Watch List, governs Data Mapper and Archiver to collect stock quote data of those stocks in the Watch List from Yahoo! Finance. The second component is Query. It provides two Queries on Alert List for backtesting purposes: Watch List Watch List is a very simple table that merely stores the stock code of its constituents. It is rather intuitive for a relational database developer to define the stock code as the primary key, isn't it? Nevertheless, remember that in Cassandra, the primary key is used to determine the node that stores the row. As Watch List is expected to not be a very long list, it would be more appropriate to put all of its rows on the same node for faster retrieval. But how can we do that? We can create an additional column, say watch_list_code, for this particular purpose. The new table is called watchlist and will be created in the packtcdma keyspace. The CQL statement is shown in chapter06_001.py: # -*- coding: utf-8 -*- # program: chapter06_001.py ## import Cassandra driver library from cassandra.cluster import Cluster ## function to create watchlist def create_watchlist(ss):    ## create watchlist table if not exists    ss.execute('CREATE TABLE IF NOT EXISTS watchlist (' +                'watch_list_code varchar,' +                'symbol varchar,' +                'PRIMARY KEY (watch_list_code, symbol))')       ## insert AAPL, AMZN, and GS into watchlist    ss.execute("INSERT INTO watchlist (watch_list_code, " +                "symbol) VALUES ('WS01', 'AAPL')")    ss.execute("INSERT INTO watchlist (watch_list_code, " +                "symbol) VALUES ('WS01', 'AMZN')")    ss.execute("INSERT INTO watchlist (watch_list_code, " +                "symbol) VALUES ('WS01', 'GS')") ## create Cassandra instance cluster = Cluster() ## establish Cassandra connection, using local default session = cluster.connect() ## use packtcdma keyspace session.set_keyspace('packtcdma') ## create watchlist table create_watchlist(session) ## close Cassandra connection cluster.shutdown() The create_watchlist function creates the table. Note that the watchlist table has a compound primary key made of watch_list_code and symbol. A Watch List called WS01 is also created, which contains three stocks, AAPL, AMZN, and GS. Alert List It is produced by a Python program and enumerates the date when the close price was above its 10-day SMA, that is, the signal and the close price at that time. Note that there were no stock code and stock name. We will create a table called alertlist to store the alerts with the code and name of the stock. The inclusion of the stock name is to meet the requirement of making the Stock Screener Application more user-friendly. Also, remember that joins are not allowed and denormalization is really the best practice in Cassandra. This means that we do not mind repeatedly storing (duplicating) the stock name in the tables that will be queried. A rule of thumb is one table for one query; as simple as that. The alertlist table is created by the CQL statement, as shown in chapter06_002.py: # -*- coding: utf-8 -*- # program: chapter06_002.py ## import Cassandra driver library from cassandra.cluster import Cluster ## function to create alertlist def create_alertlist(ss):    ## execute CQL statement to create alertlist table if not exists    ss.execute('CREATE TABLE IF NOT EXISTS alertlist (' +                'symbol varchar,' +                'price_time timestamp,' +                'stock_name varchar,' +                'signal_price float,' +                'PRIMARY KEY (symbol, price_time))') ## create Cassandra instance cluster = Cluster() ## establish Cassandra connection, using local default session = cluster.connect() ## use packtcdma keyspace session.set_keyspace('packtcdma') ## create alertlist table create_alertlist(session) ## close Cassandra connection cluster.shutdown() The primary key is also a compound primary key that consists of symbol and price_time. Adding the descriptive stock name Until now, the packtcdma keyspace has three tables, which are alertlist, quote, and watchlist. To add the descriptive stock name, one can think of only adding a column of stock name to alertlist only. As seen in the previous section, this has been done. So, do we need to add a column for quote and watchlist? It is, in fact, a design decision that depends on whether these two tables will be serving user queries. What a user query means is that the table will be used to retrieve rows for a query raised by a user. If a user wants to know the close price of Apple Inc. on June 30, 2014, it is a user query. On the other hand, if the Stock Screener Application uses a query to retrieve rows for its internal processing, it is not a user query. Therefore, if we want quote and watchlist to return rows for user queries, they need the stock name column; otherwise, they do not need it. The watchlist table is only for internal use by the current design, and so it need not have the stock name column. Of course, if in future, the Stock Screener Application allows a user to maintain Watch List, the stock name should also be added to the watchlist table. However, for quote, it is a bit tricky. As the stock name should be retrieved from the Data Feed Provider, which is Yahoo! Finance in our case, the most suitable time to get it is when the corresponding stock quote data is retrieved. Hence, a new column called stock_name is added to quote, as shown in chapter06_003.py: # -*- coding: utf-8 -*- # program: chapter06_003.py ## import Cassandra driver library from cassandra.cluster import Cluster ## function to add stock_name column def add_stockname_to_quote(ss):    ## add stock_name to quote    ss.execute('ALTER TABLE quote ' +                'ADD stock_name varchar') ## create Cassandra instance cluster = Cluster() ## establish Cassandra connection, using local default session = cluster.connect() ## use packtcdma keyspace session.set_keyspace('packtcdma') ## add stock_name column add_stockname_to_quote(session) ## close Cassandra connection cluster.shutdown() It is quite self-explanatory. Here, we use the ALTER TABLE statement to add the stock_name column of the varchar data type to quote. Queries on alerts As mentioned previously, we are interested in two questions: How many alerts were generated on a stock over a specified period of time? How many alerts were generated on a particular date? For the first question, alertlist is sufficient to provide an answer. However, alertlist cannot answer the second question because its primary key is composed of symbol and price_time. We need to create another table specifically for that question. This is an example of modeling by query. Basically, the structure of the new table for the second question should resemble the structure of alertlist. We give that table a name, alert_by_date, and create it as shown in chapter06_004.py: # -*- coding: utf-8 -*- # program: chapter06_004.py ## import Cassandra driver library from cassandra.cluster import Cluster ## function to create alert_by_date table def create_alertbydate(ss):    ## create alert_by_date table if not exists    ss.execute('CREATE TABLE IF NOT EXISTS alert_by_date (' +               'symbol varchar,' +                'price_time timestamp,' +                'stock_name varchar,' +                'signal_price float,' +                'PRIMARY KEY (price_time, symbol))') ## create Cassandra instance cluster = Cluster() ## establish Cassandra connection, using local default session = cluster.connect() ## use packtcdma keyspace session.set_keyspace('packtcdma') ## create alert_by_date table create_alertbydate(session) ## close Cassandra connection cluster.shutdown() When compared to alertlist in chapter06_002.py, alert_by_date only swaps the order of the columns in the compound primary key. One might think that a secondary index can be created on alertlist to achieve the same effect. Nonetheless, in Cassandra, a secondary index cannot be created on columns that are already engaged in the primary key. Always be aware of this constraint. We now finish the modifications on the data model. It is time for us to enhance the application logic in the next section. Summary This article extends the Stock Screener Application by a number of enhancements. We made changes to the data model to demonstrate the modeling by query techniques and how denormalization can help us achieve a high-performance application. Resources for Article: Further resources on this subject: An overview of architecture and modeling in Cassandra [Article] About Cassandra [Article] Basic Concepts and Architecture of Cassandra [Article]
Read more
  • 0
  • 0
  • 2689
article-image-performance-optimization
Packt
19 Dec 2014
30 min read
Save for later

Performance Optimization

Packt
19 Dec 2014
30 min read
In this article is written by Mark Kerzner and Sujee Maniyam, the authors of HBase Design Patterns, we will talk about how to write high performance and scalable HBase applications. In particular, will take a look at the following topics: The bulk loading of data into HBase Profiling HBase applications Tips to get good performance on writes Tips to get good performance on reads (For more resources related to this topic, see here.) Loading bulk data into HBase When deploying HBase for the first time, we usually need to import a significant amount of data. This is called initial loading or bootstrapping. There are three methods that can be used to import data into HBase, given as follows: Using the Java API to insert data into HBase. This can be done in a single client, using single or multiple threads. Using MapReduce to insert data in parallel (this approach also uses the Java API), as shown in the following diagram:  Using MapReduce to generate HBase store files in parallel in bulk and then import them into HBase directly. (This approach does not require the use of the API; it does not require code and is very efficient.)  On comparing the three methods speed wise, we have the following order: Java client < MapReduce insert < HBase file import The Java client and MapReduce use HBase APIs to insert data. MapReduce runs on multiple machines and can exploit parallelism. However, both of these methods go through the write path in HBase. Importing HBase files directly, however, skips the usual write path. HBase files already have data in the correct format that HBase understands. That's why importing them is much faster than using MapReduce and the Java client. We covered the Java API earlier. Let's start with how to insert data using MapReduce. Importing data into HBase using MapReduce MapReduce is the distributed processing engine of Hadoop. Usually, programs read/write data from HDFS. Luckily, HBase supports MapReduce. HBase can be the source and the sink for MapReduce programs. A source means MapReduce programs can read from HBase, and sink means results from MapReduce can be sent to HBase. The following diagram illustrates various sources and sinks for MapReduce:     The diagram we just saw can be summarized as follows: Scenario Source Sink Description 1 HDFS HDFS This is a typical MapReduce method that reads data from HDFS and also sends the results to HDFS. 2 HDFS HBase This imports the data from HDFS into HBase. It's a very common method that is used to import data into HBase for the first time. 3 HBase HBase Data is read from HBase and written to it. It is most likely that these will be two separate HBase clusters. It's usually used for backups and mirroring.  Importing data from HDFS into HBase Let's say we have lots of data in HDFS and want to import it into HBase. We are going to write a MapReduce program that reads from HDFS and inserts data into HBase. This is depicted in the second scenario in the table we just saw. Now, we'll be setting up the environment for the following discussion. In addition, you can find the code and the data for this discussion in our GitHub repository at https://github.com/elephantscale/hbase-book. The dataset we will use is the sensor data. Our (imaginary) sensor data is stored in HDFS as CSV (comma-separated values) text files. This is how their format looks: Sensor_id, max temperature, min temperature Here is some sample data: sensor11,90,70 sensor22,80,70 sensor31,85,72 sensor33,75,72 We have two sample files (sensor-data1.csv and sensor-data2.csv) in our repository under the /data directory. Feel free to inspect them. The first thing we have to do is copy these files into HDFS. Create a directory in HDFS as follows: $   hdfs   dfs -mkdir   hbase-import Now, copy the files into HDFS: $   hdfs   dfs   -put   sensor-data*   hbase-import/ Verify that the files exist as follows: $   hdfs   dfs -ls   hbase-import We are ready to insert this data into HBase. Note that we are designing the table to match the CSV files we are loading for ease of use. Our row key is sensor_id. We have one column family and we call it f (short for family). Now, we will store two columns, max temperature and min temperature, in this column family. Pig for MapReduce Pig allows you to write MapReduce programs at a very high level, and inserting data into HBase is just as easy. Here's a Pig script that reads the sensor data from HDFS and writes it in HBase: -- ## hdfs-to-hbase.pigdata = LOAD 'hbase-import/' using PigStorage(',') as (sensor_id:chararray, max:int, min:int);-- describe data;-- dump data; Now, store the data in hbase://sensors using the following line of code: org.apache.pig.backend.hadoop.hbase.HBaseStorage('f:max,f:min'); After creating the table, in the first command, we will load data from the hbase-import directory in HDFS. The schema for the data is defined as follows: Sensor_id : chararray (string)max : intmin : int The describe and dump statements can be used to inspect the data; in Pig, describe will give you the structure of the data object you have, and dump will output all the data to the terminal. The final STORE command is the one that inserts the data into HBase. Let's analyze how it is structured: INTO 'hbase://sensors': This tells Pig to connect to the sensors HBase table. org.apache.pig.backend.hadoop.hbase.HBaseStorage: This is the Pig class that will be used to write in HBase. Pig has adapters for multiple data stores. The first field in the tuple, sensor_id, will be used as a row key. We are specifying the column names for the max and min fields (f:max and f:min, respectively). Note that we have to specify the column family (f:) to qualify the columns. Before running this script, we need to create an HBase table called sensors. We can do this from the HBase shell, as follows: $ hbase shell$ create 'sensors' , 'f'$ quit Then, run the Pig script as follows: $ pig hdfs-to-hbase.pig Now watch the console output. Pig will execute the script as a MapReduce job. Even though we are only importing two small files here, we can insert a fairly large amount of data by exploiting the parallelism of MapReduce. At the end of the run, Pig will print out some statistics: Input(s):Successfully read 7 records (591 bytes) from: "hdfs://quickstart.cloudera:8020/user/cloudera/hbase-import"Output(s):Successfully stored 7 records in: "hbase://sensors" Looks good! We should have seven rows in our HBase sensors table. We can inspect the table from the HBase shell with the following commands: $ hbase shell$ scan 'sensors' This is how your output might look: ROW                      COLUMN+CELL sensor11                 column=f:max, timestamp=1412373703149, value=90 sensor11                 column=f:min, timestamp=1412373703149, value=70 sensor22                 column=f:max, timestamp=1412373703177, value=80 sensor22                column=f:min, timestamp=1412373703177, value=70 sensor31                 column=f:max, timestamp=1412373703177, value=85 sensor31                 column=f:min, timestamp=1412373703177, value=72 sensor33                 column=f:max, timestamp=1412373703177, value=75 sensor33                 column=f:min, timestamp=1412373703177, value=72 sensor44                 column=f:max, timestamp=1412373703184, value=55 sensor44                 column=f:min, timestamp=1412373703184, value=42 sensor45                 column=f:max, timestamp=1412373703184, value=57 sensor45                 column=f:min, timestamp=1412373703184, value=47 sensor55                 column=f:max, timestamp=1412373703184, value=55 sensor55                 column=f:min, timestamp=1412373703184, value=427 row(s) in 0.0820 seconds There you go; you can see that seven rows have been inserted! With Pig, it was very easy. It took us just two lines of Pig script to do the import. Java MapReduce We have just demonstrated MapReduce using Pig, and you now know that Pig is a concise and high-level way to write MapReduce programs. This is demonstrated by our previous script, essentially the two lines of Pig code. However, there are situations where you do want to use the Java API, and it would make more sense to use it than using a Pig script. This can happen when you need Java to access Java libraries or do some other detailed tasks for which Pig is not a good match. For that, we have provided the Java version of the MapReduce code in our GitHub repository. Using HBase's bulk loader utility HBase is shipped with a bulk loader tool called ImportTsv that can import files from HDFS into HBase tables directly. It is very easy to use, and as a bonus, it uses MapReduce internally to process files in parallel. Perform the following steps to use ImportTsv: Stage data files into HDFS (remember that the files are processed using MapReduce). Create a table in HBase if required. Run the import. Staging data files into HDFS The first step to stage data files into HDFS has already been outlined in the previous section. The following sections explain the next two steps to stage data files. Creating an HBase table We will do this from the HBase shell. A note on regions is in order here. Regions are shards created automatically by HBase. It is the regions that are responsible for the distributed nature of HBase. However, you need to pay some attention to them in order to assure performance. If you put all the data in one region, you will cause what is called region hotspotting. What is especially nice about a bulk loader is that when creating a table, it lets you presplit the table into multiple regions. Precreating regions will allow faster imports (because the insert requests will go out to multiple region servers). Here, we are creating a single column family: $ hbase shellhbase> create 'sensors', {NAME => 'f'}, {SPLITS => ['sensor20', 'sensor40', 'sensor60']}0 row(s) in 1.3940 seconds=> Hbase::Table - sensors hbase > describe 'sensors'DESCRIPTION                                       ENABLED'sensors', {NAME => 'f', DATA_BLOCK_ENCODING => true'NONE', BLOOMFILTER => 'ROW', REPLICATION_SCOPE=> '0', VERSIONS => '1', COMPRESSION => 'NONE',MIN_VERSIONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'false', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}1 row(s) in 0.1140 seconds We are creating regions here. Why there are exactly four regions will be clear from the following diagram:   On inspecting the table in the HBase Master UI, we will see this. Also, you can see how Start Key and End Key, which we specified, are showing up. Run the import Ok, now it's time to insert data into HBase. To see the usage of ImportTsv, do the following: $ hbase org.apache.hadoop.hbase.mapreduce.ImportTsv This will print the usage as follows: $ hbase org.apache.hadoop.hbase.mapreduce.ImportTsv -Dimporttsv.separator=, -Dimporttsv.columns=HBASE_ROW_KEY,f:max,f:min sensors   hbase-import/ The following table explains what the parameters mean: Parameter Description -Dimporttsv.separator Here, our separator is a comma (,). The default value is tab (t). -Dimporttsv.columns=HBASE_ROW_KEY,f:max,f:min This is where we map our input files into HBase tables. The first field, sensor_id, is our key, and we use HBASE_ROW_KEY to denote that the rest we are inserting into column family f. The second field, max temp, maps to f:max. The last field, min temp, maps to f:min. sensors This is the table name. hbase-import This is the HDFS directory where the data files are located.  When we run this command, we will see that a MapReduce job is being kicked off. This is how an import is parallelized. Also, from the console output, we can see that MapReduce is importing two files as follows: [main] mapreduce.JobSubmitter: number of splits:2 While the job is running, we can inspect the progress from YARN (or the JobTracker UI). One thing that we can note is that the MapReduce job only consists of mappers. This is because we are reading a bunch of files and inserting them into HBase directly. There is nothing to aggregate. So, there is no need for reducers. After the job is done, inspect the counters and we can see this: Map-Reduce Framework Map input records=7 Map output records=7 This tells us that mappers read seven records from the files and inserted seven records into HBase. Let's also verify the data in HBase: $   hbase shellhbase >   scan 'sensors'ROW                 COLUMN+CELLsensor11           column=f:max, timestamp=1409087465345, value=90sensor11           column=f:min, timestamp=1409087465345, value=70sensor22           column=f:max, timestamp=1409087465345, value=80sensor22           column=f:min, timestamp=1409087465345, value=70sensor31           column=f:max, timestamp=1409087465345, value=85sensor31           column=f:min, timestamp=1409087465345, value=72sensor33           column=f:max, timestamp=1409087465345, value=75sensor33           column=f:min, timestamp=1409087465345, value=72sensor44            column=f:max, timestamp=1409087465345, value=55sensor44           column=f:min, timestamp=1409087465345, value=42sensor45           column=f:max, timestamp=1409087465345, value=57sensor45           column=f:min, timestamp=1409087465345, value=47sensor55           column=f:max, timestamp=1409087465345, value=55sensor55           column=f:min, timestamp=1409087465345, value=427 row(s) in 2.1180 seconds Your output might vary slightly. We can see that seven rows are inserted, confirming the MapReduce counters! Let's take another quick look at the HBase UI, which is shown here:    As you can see, the inserts go to different regions. So, on a HBase cluster with many region servers, the load will be spread across the cluster. This is because we have presplit the table into regions. Here are some questions to test your understanding. Run the same ImportTsv command again and see how many records are in the table. Do you get duplicates? Try to find the answer and explain why that is the correct answer, then check these in the GitHub repository (https://github.com/elephantscale/hbase-book). Bulk import scenarios Here are a few bulk import scenarios: Scenario Methods Notes The data is already in HDFS and needs to be imported into HBase. The two methods that can be used to do this are as follows: If the ImportTsv tool can work for you, then use it as it will save time in writing custom MapReduce code. Sometimes, you might have to write a custom MapReduce job to import (for example, complex time series data, doing data mapping, and so on). It is probably a good idea to presplit the table before a bulk import. This spreads the insert requests across the cluster and results in a higher insert rate. If you are writing a custom MapReduce job, consider using a high-level MapReduce platform such as Pig or Hive. They are much more concise to write than the Java code. The data is in another database (RDBMs/NoSQL) and you need to import it into HBase. Use a utility such as Sqoop to bring the data into HDFS and then use the tools outlined in the first scenario. Avoid writing MapReduce code that directly queries databases. Most databases cannot handle many simultaneous connections. It is best to bring the data into Hadoop (HDFS) first and then use MapReduce. Profiling HBase applications Just like any software development process, once we have our HBase application working correctly, we would want to make it faster. At times, developers get too carried away and start optimizing before the application is finalized. There is a well-known rule that premature optimization is the root of all evil. One of the sources for this rule is Scott Meyers Effective C++. We can perform some ad hoc profiling in our code by timing various function calls. Also, we can use profiling tools to pinpoint the trouble spots. Using profiling tools is highly encouraged for the following reasons: Profiling takes out the guesswork (and a good majority of developers' guesses are wrong). There is no need to modify the code. Manual profiling means that we have to go and insert the instrumentation code all over the code. Profilers work by inspecting the runtime behavior. Most profilers have a nice and intuitive UI to visualize the program flow and time flow. The authors use JProfiler. It is a pretty effective profiler. However, it is neither free nor open source. So, for the purpose of this article, we are going to show you a simple manual profiling, as follows: public class UserInsert {      static String tableName = "users";    static String familyName = "info";      public static void main(String[] args) throws Exception {        Configuration config = HBaseConfiguration.create();        // change the following to connect to remote clusters        // config.set("hbase.zookeeper.quorum", "localhost");        long t1a = System.currentTimeMillis();        HTable htable = new HTable(config, tableName);        long t1b = System.currentTimeMillis();        System.out.println ("Connected to HTable in : " + (t1b-t1a) + " ms");        int total = 100;        long t2a = System.currentTimeMillis();        for (int i = 0; i < total; i++) {            int userid = i;            String email = "user-" + i + "@foo.com";            String phone = "555-1234";              byte[] key = Bytes.toBytes(userid);            Put put = new Put(key);              put.add(Bytes.toBytes(familyName), Bytes.toBytes("email"), Bytes.toBytes(email));            put.add(Bytes.toBytes(familyName), Bytes.toBytes("phone"), Bytes.toBytes(phone));            htable.put(put);          }        long t2b = System.currentTimeMillis();        System.out.println("inserted " + total + " users in " + (t2b - t2a) + " ms");        htable.close();      } } The code we just saw inserts some sample user data into HBase. We are profiling two operations, that is, connection time and actual insert time. A sample run of the Java application yields the following: Connected to HTable in : 1139 msinserted 100 users in 350 ms We spent a lot of time in connecting to HBase. This makes sense. The connection process has to go to ZooKeeper first and then to HBase. So, it is an expensive operation. How can we minimize the connection cost? The answer is by using connection pooling. Luckily, for us, HBase comes with a connection pool manager. The Java class for this is HConnectionManager. It is very simple to use. Let's update our class to use HConnectionManager: Code : File name: hbase_dp.ch8.UserInsert2.java   package hbase_dp.ch8;   import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.HConnection; import org.apache.hadoop.hbase.client.HConnectionManager; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.util.Bytes;   public class UserInsert2 {      static String tableName = "users";    static String familyName = "info";      public static void main(String[] args) throws Exception {        Configuration config = HBaseConfiguration.create();        // change the following to connect to remote clusters        // config.set("hbase.zookeeper.quorum", "localhost");               long t1a = System.currentTimeMillis();        HConnection hConnection = HConnectionManager.createConnection(config);        long t1b = System.currentTimeMillis();        System.out.println ("Connection manager in : " + (t1b-t1a) + " ms");          // simulate the first 'connection'        long t2a = System.currentTimeMillis();        HTableInterface htable = hConnection.getTable(tableName) ;        long t2b = System.currentTimeMillis();        System.out.println ("first connection in : " + (t2b-t2a) + " ms");               // second connection        long t3a = System.currentTimeMillis();        HTableInterface htable2 = hConnection.getTable(tableName) ;        long t3b = System.currentTimeMillis();        System.out.println ("second connection : " + (t3b-t3a) + " ms");          int total = 100;        long t4a = System.currentTimeMillis();        for (int i = 0; i < total; i++) {            int userid = i;            String email = "user-" + i + "@foo.com";            String phone = "555-1234";              byte[] key = Bytes.toBytes(userid);            Put put = new Put(key);              put.add(Bytes.toBytes(familyName), Bytes.toBytes("email"), Bytes.toBytes(email));            put.add(Bytes.toBytes(familyName), Bytes.toBytes("phone"), Bytes.toBytes(phone));            htable.put(put);          }      long t4b = System.currentTimeMillis();        System.out.println("inserted " + total + " users in " + (t4b - t4a) + " ms");        hConnection.close();    } } A sample run yields the following timings: Connection manager in : 98 ms first connection in : 808 ms second connection : 0 ms inserted 100 users in 393 ms The first connection takes a long time, but then take a look at the time of the second connection. It is almost instant ! This is cool! If you are connecting to HBase from web applications (or interactive applications), use connection pooling. More tips for high-performing HBase writes Here we will discuss some techniques and best practices to improve writes in HBase. Batch writes Currently, in our code, each time we call htable.put (one_put), we make an RPC call to an HBase region server. This round-trip delay can be minimized if we call htable.put() with a bunch of put records. Then, with one round trip, we can insert a bunch of records into HBase. This is called batch puts. Here is an example of batch puts. Only the relevant section is shown for clarity. For the full code, see hbase_dp.ch8.UserInsert3.java:        int total = 100;        long t4a = System.currentTimeMillis();        List<Put> puts = new ArrayList<>();        for (int i = 0; i < total; i++) {            int userid = i;            String email = "user-" + i + "@foo.com";            String phone = "555-1234";              byte[] key = Bytes.toBytes(userid);            Put put = new Put(key);              put.add(Bytes.toBytes(familyName), Bytes.toBytes("email"), Bytes.toBytes(email));            put.add(Bytes.toBytes(familyName), Bytes.toBytes("phone"), Bytes.toBytes(phone));                       puts.add(put); // just add to the list        }        htable.put(puts); // do a batch put        long t4b = System.currentTimeMillis();        System.out.println("inserted " + total + " users in " + (t4b - t4a) + " ms"); A sample run with a batch put is as follows: inserted 100 users in 48 ms The same code with individual puts took around 350 milliseconds! Use batch writes when you can to minimize latency. Note that the HTableUtil class that comes with HBase implements some smart batching options for your use and enjoyment. Setting memory buffers We can control when the puts are flushed by setting the client write buffer option. Once the data in the memory exceeds this setting, it is flushed to disk. The default setting is 2 M. Its purpose is to limit how much data is stored in the buffer before writing it to disk. There are two ways of setting this: In hbase-site.xml (this setting will be cluster-wide): <property>  <name>hbase.client.write.buffer</name>    <value>8388608</value>   <!-- 8 M --></property> In the application (only applies for that application): htable.setWriteBufferSize(1024*1024*10); // 10 Keep in mind that a bigger buffer takes more memory on both the client side and the server side. As a practical guideline, estimate how much memory you can dedicate to the client and put the rest of the load on the cluster. Turning off autofush If autoflush is enabled, each htable.put() object incurs a round trip RPC call to HRegionServer. Turning autoflush off can reduce the number of round trips and decrease latency. To turn it off, use this code: htable.setAutoFlush(false); The risk of turning off autoflush is if the client crashes before the data is sent to HBase, it will result in a data loss. Still, when will you want to do it? The answer is: when the danger of data loss is not important and speed is paramount. Also, see the batch write recommendations we saw previously. Turning off WAL Before we discuss this, we need to emphasize that the write-ahead log (WAL) is there to prevent data loss in the case of server crashes. By turning it off, we are bypassing this protection. Be very careful when choosing this. Bulk loading is one of the cases where turning off WAL might make sense. To turn off WAL, set it for each put: put.setDurability(Durability.SKIP_WAL); More tips for high-performing HBase reads So far, we looked at tips to write data into HBase. Now, let's take a look at some tips to read data faster. The scan cache When reading a large number of rows, it is better to set scan caching to a high number (in the 100 seconds or 1,000 seconds range). Otherwise, each row that is scanned will result in a trip to HRegionServer. This is especially encouraged for MapReduce jobs as they will likely consume a lot of rows sequentially. To set scan caching, use the following code: Scan scan = new Scan(); scan.setCaching(1000); Only read the families or columns needed When fetching a row, by default, HBase returns all the families and all the columns. If you only care about one family or a few attributes, specifying them will save needless I/O. To specify a family, use this: scan.addFamily( Bytes.toBytes("familiy1")); To specify columns, use this: scan.addColumn( Bytes.toBytes("familiy1"),   Bytes.toBytes("col1")) The block cache When scanning large rows sequentially (say in MapReduce), it is recommended that you turn off the block cache. Turning off the cache might be completely counter-intuitive. However, caches are only effective when we repeatedly access the same rows. During sequential scanning, there is no caching, and turning on the block cache will introduce a lot of churning in the cache (new data is constantly brought into the cache and old data is evicted to make room for the new data). So, we have the following points to consider: Turn off the block cache for sequential scans Turn off the block cache for random/repeated access Benchmarking or load testing HBase Benchmarking is a good way to verify HBase's setup and performance. There are a few good benchmarks available: HBase's built-in benchmark The Yahoo Cloud Serving Benchmark (YCSB) JMeter for custom workloads HBase's built-in benchmark HBase's built-in benchmark is PerformanceEvaluation. To find its usage, use this: $   hbase org.apache.hadoop.hbase.PerformanceEvaluation To perform a write benchmark, use this: $ hbase org.apache.hadoop.hbase.PerformanceEvaluation --nomapred randomWrite 5 Here we are using five threads and no MapReduce. To accurately measure the throughput, we need to presplit the table that the benchmark writes to. It is TestTable. $ hbase org.apache.hadoop.hbase.PerformanceEvaluation --nomapred --presplit=3 randomWrite 5 Here, the table is split in three ways. It is good practice to split the table into as many regions as the number of region servers. There is a read option along with a whole host of scan options. YCSB The YCSB is a comprehensive benchmark suite that works with many systems such as Cassandra, Accumulo, and HBase. Download it from GitHub, as follows: $   git clone git://github.com/brianfrankcooper/YCSB.git Build it like this: $ mvn -DskipTests package Create an HBase table to test against: $ hbase shellhbase> create 'ycsb', 'f1' Now, copy hdfs-site.xml for your cluster into the hbase/src/main/conf/ directory and run the benchmark: $ bin/ycsb load hbase -P workloads/workloada -p columnfamily=f1 -p table=ycsb YCSB offers lots of workloads and options. Please refer to its wiki page at https://github.com/brianfrankcooper/YCSB/wiki. JMeter for custom workloads The standard benchmarks will give you an idea of your HBase cluster's performance. However, nothing can substitute measuring your own workload. We want to measure at least the insert speed or the query speed. We also want to run a stress test. So, we can measure the ceiling on how much our HBase cluster can support. We can do a simple instrumentation as we did earlier too. However, there are tools such as JMeter that can help us with load testing. Please refer to the JMeter website and check out the Hadoop or HBase plugins for JMeter. Monitoring HBase Running any distributed system involves decent monitoring. HBase is no exception. Luckily, HBase has the following capabilities: HBase exposes a lot of metrics These metrics can be directly consumed by monitoring systems such as Ganglia We can also obtain these metrics in the JSON format via the REST interface and JMX Monitoring is a big subject and we consider it as part HBase administration. So, in this section, we will give pointers to tools and utilities that allow you to monitor HBase. Ganglia Ganglia is a generic system monitor that can monitor hosts (such as CPU, disk usage, and so on). The Hadoop stack has had a pretty good integration with Ganglia for some time now. HBase and Ganglia integration is set up by modern installers from Cloudera and Hortonworks. To enable Ganglia metrics, update the hadoop-metrics.properties file in the HBase configuration directory. Here's a sample file: hbase.class=org.apache.hadoop.metrics.ganglia.GangliaContext31 hbase.period=10 hbase.servers=ganglia-server:PORT jvm.class=org.apache.hadoop.metrics.ganglia.GangliaContext31 jvm.period=10 jvm.servers=ganglia-server:PORT rpc.class=org.apache.hadoop.metrics.ganglia.GangliaContext31 rpc.period=10 rpc.servers=ganglia-server:PORT This file has to be uploaded to all the HBase servers (master servers as well as region servers). Here are some sample graphs from Ganglia (these are Wikimedia statistics, for example): These graphs show cluster-wide resource utilization. OpenTSDB OpenTSDB is a scalable time series database. It can collect and visualize metrics on a large scale. OpenTSDB uses collectors, light-weight agents that send metrics to the open TSDB server to collect metrics, and there is a collector library that can collect metrics from HBase. You can see all the collectors at http://opentsdb.net/docs/build/html/user_guide/utilities/tcollector.html. An interesting factoid is that OpenTSDB is built on Hadoop/HBase. Collecting metrics via the JMX interface HBase exposes a lot of metrics via JMX. This page can be accessed from the web dashboard at http://<hbase master>:60010/jmx. For example, for a HBase instance that is running locally, it will be http://localhost:60010/jmx. Here is a sample screenshot of the JMX metrics via the web UI: Here's a quick example of how to programmatically retrieve these metrics using curl: $ curl 'localhost:60010/jmx' Since this is a web service, we can write a script/application in any language (Java, Python, or Ruby) to retrieve and inspect the metrics. Summary In this article, you learned how to push the performance of our HBase applications up. We looked at how to effectively load a large amount of data into HBase. You also learned about benchmarking and monitoring HBase and saw tips on how to do high-performing reads/writes. Resources for Article:   Further resources on this subject: The HBase's Data Storage [article] Hadoop and HDInsight in a Heartbeat [article] Understanding the HBase Ecosystem [article]
Read more
  • 0
  • 0
  • 10649

article-image-sharing-mvvmcross
Packt
19 Dec 2014
36 min read
Save for later

Sharing with MvvmCross

Packt
19 Dec 2014
36 min read
In this article by Mark Reynolds, author of the book Xamarin Essentials, we will take the next step and look at how the use of design patterns and frameworks can increase the amount of code that can be reused. We will cover the following topics: An introduction to MvvmCross The MVVM design pattern Core concepts Views, ViewModels, and commands Data binding Navigation (ViewModel to ViewModel) The project organization The startup process Creating NationalParks.MvvmCross It's more than a little ambitious to try to cover MvvmCross along with a working example in a single article. Our approach will be to introduce the core concepts at a high level and then dive in and create the national parks sample app using MvvmCross. This will give you a basic understanding of how to use the framework and the value associated with its use. With that in mind, let's get started. (For more resources related to this topic, see here.) Introducing MvvmCross                                            MvvmCross is an open source framework that was created by Stuart Lodge. It is based on the Model-View-ViewModel (MVVM) design pattern and is designed to enhance code reuse across numerous platforms, including Xamarin.Android, Xamarin.iOS, Windows Phone, Windows Store, WPF, and Mac OSX. The MvvmCross project is hosted on GitHub and can be accessed at https://github.com/MvvmCross/MvvmCross. The MVVM pattern MVVM is a variation of the Model-View-Controller pattern. It separates logic traditionally placed in a View object into two distinct objects, one called View and the other called ViewModel. The View is responsible for providing the user interface and the ViewModel is responsible for the presentation logic. The presentation logic includes transforming data from the Model into a form that is suitable for the user interface to work with and mapping user interaction with the View into requests sent back to the Model. The following diagram depicts how the various objects in MVVM communicate: While MVVM presents a more complex implementation model, there are significant benefits of it, which are as follows: ViewModels and their interactions with Models can generally be tested using frameworks (such as NUnit) that are much easier than applications that combine the user interface and presentation layers ViewModels can generally be reused across different user interface technologies and platforms These factors make the MVVM approach both flexible and powerful. Views Views in an MvvmCross app are implemented using platform-specific constructs. For iOS apps, Views are generally implemented as ViewControllers and XIB files. MvvmCross provides a set of base classes, such as MvxViewContoller, that iOS ViewControllers inherit from. Storyboards can also be used in conjunction with a custom presenter to create Views; we will briefly discuss this option in the section titled Implementing the iOS user interface later in this article. For Android apps, Views are generally implemented as MvxActivity or MvxFragment along with their associated layout files. ViewModels ViewModels are classes that provide data and presentation logic to views in an app. Data is exposed to a View as properties on a ViewModel, and logic that can be invoked from a View is exposed as commands. ViewModels inherit from the MvxViewModel base class. Commands Commands are used in ViewModels to expose logic that can be invoked from the View in response to user interactions. The command architecture is based on the ICommand interface used in a number of Microsoft frameworks such as Windows Presentation Foundation (WPF) and Silverlight. MvvmCross provides IMvxCommand, which is an extension of ICommand, along with an implementation named MvxCommand. The commands are generally defined as properties on a ViewModel. For example: public IMvxCommand ParkSelected { get; protected set; } Each command has an action method defined, which implements the logic to be invoked: protected void ParkSelectedExec(NationalPark park){   . . .// logic goes here} The commands must be initialized and the corresponding action method should be assigned: ParkSelected =   new MvxCommand<NationalPark> (ParkSelectedExec); Data binding Data binding facilitates communication between the View and the ViewModel by establishing a two-way link that allows data to be exchanged. The data binding capabilities provided by MvvmCross are based on capabilities found in a number of Microsoft XAML-based UI frameworks such as WPF and Silverlight. The basic idea is that you would like to bind a property in a UI control, such as the Text property of an EditText control in an Android app to a property of a data object such as the Description property of NationalPark. The following diagram depicts this scenario:  The binding modes There are four different binding modes that can be used for data binding: OneWay binding: This mode tells the data binding framework to transfer values from the ViewModel to the View and transfer any updates to properties on the ViewModel to their bound View property. OneWayToSource binding: This mode tells the data binding framework to transfer values from the View to the ViewModel and transfer updates to View properties to their bound ViewModel property. TwoWay binding: This mode tells the data binding framework to transfer values in both directions between the ViewModel and View, and updates on either object will cause the other to be updated. This binding mode is useful when values are being edited. OneTime binding: This mode tells the data binding framework to transfer values from ViewModel to View when the binding is established; in this mode, updates to ViewModel properties are not monitored by the View. The INotifyPropertyChanged interface The INotifyPropertyChanged interface is an integral part of making data binding work effectively; it acts as a contract between the source object and the target object. As the name implies, it defines a contract that allows the source object to notify the target object when data has changed, thus allowing the target to take any necessary actions such as refreshing its display. The interface consists of a single event—the PropertyChanged event—that the target object can subscribe to and that is triggered by the source if a property changes. The following sample demonstrates how to implement INotifyPropertyChanged: public class NationalPark : INotifyPropertyChanged{  public event PropertyChangedEventHandler     PropertyChanged;// rather than use "… code" it is safer to use// the comment formstring _name;public string Name{   get { return _name; }   set   {       if (value.Equals (_name,           StringComparison.Ordinal))      {     // Nothing to do - the value hasn't changed;     return;       }       _name = value;       OnPropertyChanged();   }}. . .void OnPropertyChanged(   [CallerMemberName] string propertyName = null){     var handler = PropertyChanged;if (handler != null){     handler(this,           new PropertyChangedEventArgs(propertyName));}}} Binding specifications Bindings can be specified in a couple of ways. For Android apps, bindings can be specified in layout files. The following example demonstrates how to bind the Text property of a TextView instance to the Description property in a NationalPark instance: <TextView   android_layout_width="match_parent"   android_layout_height="wrap_content"   android_id="@+id/descrTextView"  local:MvxBind="Text Park.Description" /> For iOS, binding must be accomplished using the binding API. CreateBinding() is a method than can be found on MvxViewController. The following example demonstrates how to bind the Description property to a UILabel instance: this.CreateBinding (this.descriptionLabel).   To ((DetailViewModel vm) => vm.Park.Description).   Apply (); Navigating between ViewModels Navigating between various screens within an app is an important capability. Within a MvvmCross app, this is implemented at the ViewModel level so that navigation logic can be reused. MvvmCross supports navigation between ViewModels through use of the ShowViewModel<T>() method inherited from MvxNavigatingObject, which is the base class for MvxViewModel. The following example demonstrates how to navigate to DetailViewModel: ShowViewModel<DetailViewModel>(); Passing parameters In many situations, there is a need to pass information to the destination ViewModel. MvvmCross provides a number of ways to accomplish this. The primary method is to create a class that contains simple public properties and passes an instance of the class into ShowViewModel<T>(). The following example demonstrates how to define and use a parameters class during navigation: public class DetailParams{   public int ParkId { get; set; }} // using the parameters classShowViewModel<DetailViewModel>(new DetailViewParam() { ParkId = 0 }); To receive and use parameters, the destination ViewModel implements an Init() method that accepts an instance of the parameters class: public class DetailViewModel : MvxViewModel{   . . .   public void Init(DetailViewParams parameters)   {       // use the parameters here . . .   }} Solution/project organization Each MvvmCross solution will have a single core PCL project that houses the reusable code and a series of platform-specific projects that contain the various apps. The following diagram depicts the general structure: The startup process MvvmCross apps generally follow a standard startup sequence that is initiated by platform-specific code within each app. There are several classes that collaborate to accomplish the startup; some of these classes reside in the core project and some of them reside in the platform-specific projects. The following sections describe the responsibilities of each of the classes involved. App.cs The core project has an App class that inherits from MvxApplication. The App class contains an override to the Initialize() method so that at a minimum, it can register the first ViewModel that should be presented when the app starts: RegisterAppStart<ViewModels.MasterViewModel>(); Setup.cs Android and iOS projects have a Setup class that is responsible for creating the App object from the core project during the startup. This is accomplished by overriding the CreateApp() method: protected override IMvxApplication CreateApp(){   return new Core.App();} For Android apps, Setup inherits from MvxAndroidSetup. For iOS apps, Setup inherits from MvxTouchSetup. The Android startup Android apps are kicked off using a special Activity splash screen that calls the Setup class and initiates the MvvmCross startup process. This is all done automatically for you; all you need to do is include the splash screen definition and make sure it is marked as the launch activity. The definition is as follows: [Activity(Label="NationalParks.Droid", MainLauncher = true,Icon="@drawable/icon", Theme="@style/Theme.Splash",NoHistory=true,ScreenOrientation = ScreenOrientation.Portrait)]public class SplashScreen : MvxSplashScreenActivity{   public SplashScreen():base(Resource.Layout.SplashScreen)   {   } The iOS startup The iOS app startup is slightly less automated and is initiated from within the FinishedLaunching() method of AppDelegate: public override bool FinishedLaunching (   UIApplication app, NSDictionary options){   _window = new UIWindow (UIScreen.MainScreen.Bounds);    var setup = new Setup(this, _window);   setup.Initialize();   var startup = Mvx.Resolve<IMvxAppStart>();   startup.Start();    _window.MakeKeyAndVisible ();   return true;} Creating NationalParks.MvvmCross Now that we have basic knowledge of the MvvmCross framework, let's put that knowledge to work and convert the NationalParks app to leverage the capabilities we just learned. Creating the MvvmCross core project We will start by creating the core project. This project will contain all the code that will be shared between the iOS and Android app primarily in the form of ViewModels. The core project will be built as a Portable Class Library. To create NationalParks.Core, perform the following steps: From the main menu, navigate to File | New Solution. From the New Solution dialog box, navigate to C# | Portable Library, enter NationalParks.Core for the project Name field, enter NationalParks.MvvmCross for the Solution field, and click on OK. Add the MvvmCross starter package to the project from Nuget. Select the NationalParks.Core project and navigate to Project | Add Packages from the main menu. Enter MvvmCross starter in the search field. Select the MvvmCross – Hot Tuna Starter Pack entry and click on Add Package. A number of things were added to NationalParks.Core as a result of adding the package, and they are as follows: A packages.config file, which contains a list of libraries (dlls) associated with the MvvmCross starter kit package. These entries are links to actual libraries in the Packages folder of the overall solution. A ViewModels folder with a sample ViewModel named FirstViewModel. An App class in App.cs, which contains an Initialize() method that starts the MvvmCross app by calling RegisterAppStart() to start FirstViewModel. We will eventually be changing this to start the MasterViewModel class, which will be associated with a View that lists national parks. Creating the MvvmCross Android app The next step is to create an Android app project in the same solution. To create NationalParks.Droid, complete the following steps: Select the NationalParks.MvvmCross solution, right-click on it, and navigate to Add | New Project. From the New Project dialog box, navigate to C# | Android | Android Application, enter NationalParks.Droid for the Name field, and click on OK. Add the MvvmCross starter kit package to the new project by selecting NationalParks.Droid and navigating to Project | Add Packages from the main menu. A number of things were added to NationalParks.Droid as a result of adding the package, which are as follows: packages.config: This file contains a list of libraries (dlls) associated with the MvvmCross starter kit package. These entries are links to an actual library in the Packages folder of the overall solution, which contains the actual downloaded libraries. FirstView : This class is present in the Views folder, which corresponds to FirstViewModel, which was created in NationalParks.Core. FirstView: This layout is present in Resourceslayout, which is used by the FirstView activity. This is a traditional Android layout file with the exception that it contains binding declarations in the EditView and TextView elements. Setup: This file inherits from MvxAndroidSetup. This class is responsible for creating an instance of the App class from the core project, which in turn displays the first ViewModel via a call to RegisterAppStart(). SplashScreen: This class inherits from MvxSplashScreenActivity. The SplashScreen class is marked as the main launcher activity and thus initializes the MvvmCross app with a call to Setup.Initialize(). Add a reference to NationalParks.Core by selecting the References folder, right-click on it, select Edit References, select the Projects tab, check NationalParks.Core, and click on OK. Remove MainActivity.cs as it is no longer needed and will create a build error. This is because it is marked as the main launch and so is the new SplashScreen class. Also, remove the corresponding Resourceslayoutmain.axml layout file. Run the app. The app will present FirstViewModel, which is linked to the corresponding FirstView instance with an EditView class, and TextView presents the same Hello MvvmCross text. As you edit the text in the EditView class, the TextView class is automatically updated by means of data binding. The following screenshot depicts what you should see: Reusing NationalParks.PortableData and NationalParks.IO Before we start creating the Views and ViewModels for our app, we first need to bring in some code from our previous efforts that can be used to maintain parks. For this, we will simply reuse the NationalParksData singleton and the FileHandler classes that were created previously. To reuse the NationalParksData singleton and FileHandler classes, complete the following steps: Copy NationalParks.PortableData and NationalParks.IO to the NationalParks.MvvmCross solution folder. Add a reference to NationalParks.PortableData in the NationalParks.Droid project. Create a folder named NationalParks.IO in the NationalParks.Droid project and add a link to FileHandler.cs from the NationalParks.IO project. Recall that the FileHandler class cannot be contained in the Portable Class Library because it uses file IO APIs that cannot be references from a Portable Class Library. Compile the project. The project should compile cleanly now. Implementing the INotifyPropertyChange interface We will be using data binding to bind UI controls to the NationalPark object and thus, we need to implement the INotifyPropertyChange interface. This ensures that changes made to properties of a park are reported to the appropriate UI controls. To implement INotifyPropertyChange, complete the following steps: Open NationalPark.cs in the NationalParks.PortableData project. Specify that the NationalPark class implements INotifyPropertyChanged interface. Select the INotifyPropertyChanged interface, right-click on it, navigate to Refactor | Implement interface, and press Enter. Enter the following code snippet: public class NationalPark : INotifyPropertyChanged{   public event PropertyChangedEventHandler       PropertyChanged;   . . .} Add an OnPropertyChanged() method that can be called from each property setter method: void OnPropertyChanged(   CallerMemberName] string propertyName = null){   var handler = PropertyChanged;   if (handler != null)   {       handler(this,           new PropertyChangedEventArgs(propertyName));   }} Update each property definition to call the setter in the same way as it is depicted for the Name property: string _name;public string Name{get { return _name; }set{   if (value.Equals (_name, StringComparison.Ordinal))   {     // Nothing to do - the value hasn't changed;return;   }   _name = value;   OnPropertyChanged();}} Compile the project. The project should compile cleanly. We are now ready to use the NationalParksData singleton in our new project, and it supports data binding. Implementing the Android user interface Now, we are ready to create the Views and ViewModels required for our app. The app we are creating will follow the following flow: A master list view to view national parks A detail view to view details of a specific park An edit view to edit a new or previously existing park The process for creating views and ViewModels in an Android app generally consists of three different steps: Create a ViewModel in the core project with the data and event handlers (commands) required to support the View. Create an Android layout with visual elements and data binding specifications. Create an Android activity, which corresponds to the ViewModel and displays the layout. In our case, this process will be slightly different because we will reuse some of our previous work, specifically, the layout files and the menu definitions. To reuse layout files and menu definitions, perform the following steps: Copy Master.axml, Detail.axml, and Edit.axml from the Resourceslayout folder of the solution to the Resourceslayout folder in the NationalParks.Droid project, and add them to the project by selecting the layout folder and navigating to Add | Add Files. Copy MasterMenu.xml, DetailMenu.xml, and EditMenu.xml from the Resourcesmenu folder of the solution to the Resourcesmenu folder in the NationalParks.Droid project, and add them to the project by selecting the menu folder and navigating to Add | Add Files. Implementing the master list view We are now ready to implement the first of our View/ViewModel combinations, which is the master list view. Creating MasterViewModel The first step is to create a ViewModel and add a property that will provide data to the list view that displays national parks along with some initialization code. To create MasterViewModel, complete the following steps: Select the ViewModels folder in NationalParks.Core, right-click on it, and navigate to Add | New File. In the New File dialog box, navigate to General | Empty Class, enter MasterViewModel for the Name field, and click on New. Modify the class definition so that MasterViewModel inherits from MvxViewModel; you will also need to add a few using directives: . . .using Cirrious.CrossCore.Platform;using Cirrious.MvvmCross.ViewModels;. . .namespace NationalParks.Core.ViewModels{public class MasterViewModel : MvxViewModel{         . . .   }} Add a property that is a list of NationalPark elements to MasterViewModel. This property will later be data-bound to a list view: private List<NationalPark> _parks;public List<NationalPark> Parks{   get { return _parks; }   set { _parks = value;         RaisePropertyChanged(() => Parks);   }} Override the Start() method on MasterViewModel to load the _parks collection with data from the NationalParksData singleton. You will need to add a using directive for the NationalParks.PortableData namespace again: . . .using NationalParks.PortableData;. . .public async override void Start (){   base.Start ();   await NationalParksData.Instance.Load ();   Parks = new List<NationalPark> (       NationalParksData.Instance.Parks);} We now need to modify the app startup sequence so that MasterViewModel is the first ViewModel that's started. Open App.cs in NationalParks.Core and change the call to RegisterAppStart() to reference MasterViewModel: RegisterAppStart<ViewModels.MasterViewModel>(); Updating the Master.axml layout Update Master.axml so that it can leverage the data binding capabilities provided by MvvmCross. To update Master.axml, complete the following steps: Open Master.axml and add a namespace definition to the top of the XML to include the NationalParks.Droid namespace: This namespace definition is required in order to allow Android to resolve the MvvmCross-specific elements that will be specified. Change the ListView element to a Mvx.MvxListView element: <Mvx.MvxListView   android_layout_width="match_parent"   android_layout_height="match_parent"   android_id="@+id/parksListView" /> Add a data binding specification to the MvxListView element, binding the ItemsSource property of the list view to the Parks property of MasterViewModel, as follows:    . . .   android_id="@+id/parksListView" local_MvxBind="ItemsSource Parks" /> Add a list item template attribute to the element definition. This layout controls the content of each item that will be displayed in the list view: local:MvxItemTemplate="@layout/nationalparkitem" Create the NationalParkItem layout and provide TextView elements to display both the name and description of a park, as follows: <LinearLayout    android_orientation="vertical"   android_layout_width="fill_parent"   android_layout_height="wrap_content">   <TextView       android_layout_width="match_parent"       android_layout_height="wrap_content"       android_textSize="40sp"/>   <TextView       android_layout_width="match_parent"       android_layout_height="wrap_content"       android_textSize="20sp"/></LinearLayout> Add data binding specifications to each of the TextView elements: . . .       local_MvxBind="Text Name" />. . .       local_MvxBind="Text Description" />. . . Note that in this case, the context for data binding is an instance of an item in the collection that was bound to MvxListView, for this example, an instance of NationalPark. Creating the MasterView activity Next, create MasterView, which is an MvxActivity instance that corresponds with MasterViewModel. To create MasterView, complete the following steps: Select the ViewModels folder in NationalParks.Core, right-click on it, navigate to Add | New File. In the New File dialog, navigate to Android | Activity, enter MasterView in the Name field, and select New. Modify the class specification so that it inherits from MvxActivity; you will also need to add a few using directives as follows: using Cirrious.MvvmCross.Droid.Views;using NationalParks.Core.ViewModels;. . .namespace NationalParks.Droid.Views{   [Activity(Label = "Parks")]   public class MasterView : MvxActivity   {       . . .   }} Open Setup.cs and add code to initialize the file handler and path for the NationalParksData singleton to the CreateApp() method, as follows: protected override IMvxApplication CreateApp(){   NationalParksData.Instance.FileHandler =       new FileHandler ();   NationalParksData.Instance.DataDir =       System.Environment.GetFolderPath(         System.Environment.SpecialFolder.MyDocuments);   return new Core.App();} Compile and run the app; you will need to copy the NationalParks.json file to the device or emulator using the Android Device Monitor. All the parks in NationalParks.json should be displayed. Implementing the detail view Now that we have the master list view displaying national parks, we can focus on creating the detail view. We will follow the same steps for the detail view as the ones we just completed for the master view. Creating DetailViewModel We start creating DetailViewModel by using the following steps: Following the same procedure as the one that was used to create MasterViewModel, create a new ViewModel named DetailViewModel in the ViewModel folder of NationalParks.Core. Add a NationalPark property to support data binding for the view controls, as follows: protected NationalPark _park;public NationalPark Park{   get { return _park; }   set { _park = value;         RaisePropertyChanged(() => Park);     }} Create a Parameters class that can be used to pass a park ID for the park that should be displayed. It's convenient to create this class within the class definition of the ViewModel that the parameters are for: public class DetailViewModel : MvxViewModel{   public class Parameters   {       public string ParkId { get; set; }   }   . . . Implement an Init() method that will accept an instance of the Parameters class and get the corresponding national park from NationalParkData: public void Init(Parameters parameters){   Park = NationalParksData.Instance.Parks.       FirstOrDefault(x => x.Id == parameters.ParkId);} Updating the Detail.axml layout Next, we will update the layout file. The main changes that need to be made are to add data binding specifications to the layout file. To update the Detail.axml layout, perform the following steps: Open Detail.axml and add the project namespace to the XML file: Add data binding specifications to each of the TextView elements that correspond to a national park property, as demonstrated for the park name: <TextView   android_layout_width="match_parent"   android_layout_height="wrap_content"   android_id="@+id/nameTextView"   local_MvxBind="Text Park.Name" /> Creating the DetailView activity Now, create the MvxActivity instance that will work with DetailViewModel. To create DetailView, perform the following steps: Following the same procedure as the one that was used to create MasterView, create a new view named DetailView in the Views folder of NationalParks.Droid. Implement the OnCreateOptionsMenu() and OnOptionsItemSelected() methods so that our menus will be accessible. Copy the implementation of these methods from the solution. Comment out the section in OnOptionsItemSelect() related to the Edit action for now; we will fill that in once the edit view is completed. Adding navigation The last step is to add navigation so that when an item is clicked on in MvxListView on MasterView, the park is displayed in the detail view. We will accomplish this using a command property and data binding. To add navigation, perform the following steps: Open MasterViewModel and add an IMvxCommand property; this will be used to handle a park that is being selected: protected IMvxCommand ParkSelected { get; protected set; } Create an Action delegate that will be called when the ParkSelected command is executed, as follows: protected void ParkSelectedExec(NationalPark park){   ShowViewModel<DetailViewModel> (       new DetailViewModel.Parameters ()           { ParkId = park.Id });} Initialize the command property in the constructor of MasterViewModel: ParkClicked =   new MvxCommand<NationalPark> (ParkSelectedExec); Now, for the last step, add a data binding specification to MvvListView in Master.axml to bind the ItemClick event to the ParkClicked command on MasterViewModel, which we just created: local:MvxBind="ItemsSource Parks; ItemClick ParkClicked" Compile and run the app. Clicking on a park in the list view should now navigate to the detail view, displaying the selected park. Implementing the edit view We are now almost experts at implementing new Views and ViewModels. One last View to go is the edit view. Creating EditViewModel Like we did previously, we start with the ViewModel. To create EditViewModel, complete the following steps: Following the same process that was previously used in this article to create EditViewModel, add a data binding property and create a Parameters class for navigation. Implement an Init() method that will accept an instance of the Parameters class and get the corresponding national park from NationalParkData in the case of editing an existing park or create a new instance if the user has chosen the New action. Inspect the parameters passed in to determine what the intent is: public void Init(Parameters parameters){   if (string.IsNullOrEmpty (parameters.ParkId))       Park = new NationalPark ();   else       Park =           NationalParksData.Instance.           Parks.FirstOrDefault(           x => x.Id == parameters.ParkId);} Updating the Edit.axml layout Update Edit.axml to provide data binding specifications. To update the Edit.axml layout, you first need to open Edit.axml and add the project namespace to the XML file. Then, add the data binding specifications to each of the EditView elements that correspond to a national park property. Creating the EditView activity Create a new MvxActivity instance named EditView to will work with EditViewModel. To create EditView, perform the following steps: Following the same procedure as the one that was used to create DetailView, create a new View named EditView in the Views folder of NationalParks.Droid. Implement the OnCreateOptionsMenu() and OnOptionsItemSelected() methods so that the Done action will accessible from the ActionBar. You can copy the implementation of these methods from the solution. Change the implementation of Done to call the Done command on EditViewModel. Adding navigation Add navigation to two places: when New (+) is clicked from MasterView and when Edit is clicked in DetailView. Let's start with MasterView. To add navigation from MasterViewModel, complete the following steps: Open MasterViewModel.cs and add a NewParkClicked command property along with the handler for the command. Be sure to initialize the command in the constructor, as follows: protected IMvxCommand NewParkClicked { get; set; }protected void NewParkClickedExec(){ShowViewModel<EditViewModel> ();} Note that we do not pass in a parameter class into ShowViewModel(). This will cause a default instance to be created and passed in, which means that ParkId will be null. We will use this as a way to determine whether a new park should be created. Now, it's time to hook the NewParkClicked command up to the actionNew menu item. We do not have a way to accomplish this using data binding, so we will resort to a more traditional approach—we will use the OnOptionsItemSelected() method. Add logic to invoke the Execute() method on NewParkClicked, as follows: case Resource.Id.actionNew:   ((MasterViewModel)ViewModel).       NewParkClicked.Execute ();   return true; To add navigation from DetailViewModel, complete the following steps: Open DetailViewModel.cs and add a EditParkClicked command property along with the handler for the command. Be sure to initialize the command in the constructor, as shown in the following code snippet: protected IMvxCommand EditPark { get; protected set;}protected void EditParkHandler(){   ShowViewModel<EditViewModel> (       new EditViewModel.Parameters ()          { ParkId = _park.Id });} Note that an instance of the Parameters class is created, initialized, and passed into the ShowViewModel() method. This instance will in turn be passed into the Init() method on EditViewModel. Initialize the command property in the constructor for MasterViewModel, as follows: EditPark =    new MvxCommand<NationalPark> (EditParkHandler); Now, update the OnOptionsItemSelect() method in DetailView to invoke the DetailView.EditPark command when the Edit action is selected: case Resource.Id.actionEdit:   ((DetailViewModel)ViewModel).EditPark.Execute ();   return true; Compile and run NationalParks.Droid. You should now have a fully functional app that has the ability to create new parks and edit the existing parks. Changes made to EditView should automatically be reflected in MasterView and DetailView. Creating the MvvmCross iOS app The process of creating the Android app with MvvmCross provides a solid understanding of how the overall architecture works. Creating the iOS solution should be much easier for two reasons: first, we understand how to interact with MvvmCross and second, all the logic we have placed in NationalParks.Core is reusable, so that we just need to create the View portion of the app and the startup code. To create NationalParks.iOS, complete the following steps: Select the NationalParks.MvvmCross solution, right-click on it, and navigate to Add | New Project. From the New Project dialog, navigate to C# | iOS | iPhone | Single View Application, enter NationalParks.iOS in the Name field, and click on OK. Add the MvvmCross starter kit package to the new project by selecting NationalParks.iOS and navigating to Project | Add Packages from the main menu. A number of things were added to NationalParks.iOS as a result of adding the package. They are as follows: packages.config: This file contains a list of libraries associated with the MvvmCross starter kit package. These entries are links to an actual library in the Packages folder of the overall solution, which contains the actual downloaded libraries. FirstView: This class is placed in the Views folder, which corresponds to the FirstViewModel instance created in NationalParks.Core. Setup: This class inherits from MvxTouchSetup. This class is responsible for creating an instance of the App class from the core project, which in turn displays the first ViewModel via a call to RegisterAppStart(). AppDelegate.cs.txt: This class contains the sample startup code, which should be placed in the actual AppDelete.cs file. Implementing the iOS user interface We are now ready to create the user interface for the iOS app. The good news is that we already have all the ViewModels implemented, so we can simply reuse them. The bad news is that we cannot easily reuse the storyboards from our previous work; MvvmCross apps generally use XIB files. One of the reasons for this is that storyboards are intended to provide navigation capabilities and an MvvmCross app delegates that responsibility to ViewModel and presenter. It is possible to use storyboards in combination with a custom presenter, but the remainder of this article will focus on using XIB files, as this is the more common use. The screen layouts can be used as depicted in the following screenshot: We are now ready to get started. Implementing the master view The first view we will work on is the master view. To implement the master view, complete the following steps: Create a new ViewController class named MasterView by right-clicking on the Views folder of NationalParks.iOS and navigating to Add | New File | iOS | iPhone View Controller. Open MasterView.xib and arrange controls as seen in the screen layouts. Add outlets for each of the edit controls. Open MasterView.cs and add the following boilerplate logic to deal with constraints on iOS 7, as follows: // ios7 layoutif (RespondsToSelector(new   Selector("edgesForExtendedLayout")))   EdgesForExtendedLayout = UIRectEdge.None; Within the ViewDidLoad() method, add logic to create MvxStandardTableViewSource for parksTableView: MvxStandardTableViewSource _source;. . ._source = new MvxStandardTableViewSource(   parksTableView,   UITableViewCellStyle.Subtitle,   new NSString("cell"),   "TitleText Name; DetailText Description",     0);parksTableView.Source = _source; Note that the example uses the Subtitle cell style and binds the national park name and description to the title and subtitle. Add the binding logic to the ViewDidShow() method. In the previous step, we provided specifications for properties of UITableViewCell to properties in the binding context. In this step, we need to set the binding context for the Parks property on MasterModelView: var set = this.CreateBindingSet<MasterView,   MasterViewModel>();set.Bind (_source).To (vm => vm.Parks);set.Apply(); Compile and run the app. All the parks in NationalParks.json should be displayed. Implementing the detail view Now, implement the detail view using the following steps: Create a new ViewController instance named DetailView. Open DetailView.xib and arrange controls as shown in the following code. Add outlets for each of the edit controls. Open DetailView.cs and add the binding logic to the ViewDidShow() method: this.CreateBinding (this.nameLabel).   To ((DetailViewModel vm) => vm.Park.Name).Apply ();this.CreateBinding (this.descriptionLabel).   To ((DetailViewModel vm) => vm.Park.Description).       Apply ();this.CreateBinding (this.stateLabel).   To ((DetailViewModel vm) => vm.Park.State).Apply ();this.CreateBinding (this.countryLabel).   To ((DetailViewModel vm) => vm.Park.Country).       Apply ();this.CreateBinding (this.latLabel).   To ((DetailViewModel vm) => vm.Park.Latitude).        Apply ();this.CreateBinding (this.lonLabel).   To ((DetailViewModel vm) => vm.Park.Longitude).       Apply (); Adding navigation Add navigation from the master view so that when a park is selected, the detail view is displayed, showing the park. To add navigation, complete the following steps: Open MasterView.cs, create an event handler named ParkSelected, and assign it to the SelectedItemChanged event on MvxStandardTableViewSource, which was created in the ViewDidLoad() method: . . .   _source.SelectedItemChanged += ParkSelected;. . .protected void ParkSelected(object sender, EventArgs e){   . . .} Within the event handler, invoke the ParkSelected command on MasterViewModel, passing in the selected park: ((MasterViewModel)ViewModel).ParkSelected.Execute (       (NationalPark)_source.SelectedItem); Compile and run NationalParks.iOS. Selecting a park in the list view should now navigate you to the detail view, displaying the selected park. Implementing the edit view We now need to implement the last of the Views for the iOS app, which is the edit view. To implement the edit view, complete the following steps: Create a new ViewController instance named EditView. Open EditView.xib and arrange controls as in the layout screenshots. Add outlets for each of the edit controls. Open EditView.cs and add the data binding logic to the ViewDidShow() method. You should use the same approach to data binding as the approach used for the details view. Add an event handler named DoneClicked, and within the event handler, invoke the Done command on EditViewModel: protected void DoneClicked (object sender, EventArgs e){  ((EditViewModel)ViewModel).Done.Execute();} In ViewDidLoad(), add UIBarButtonItem to NavigationItem for EditView, and assign the DoneClicked event handler to it, as follows: NavigationItem.SetRightBarButtonItem(   new UIBarButtonItem(UIBarButtonSystemItem.Done,       DoneClicked), true); Adding navigation Add navigation to two places: when New (+) is clicked from the master view and when Edit is clicked on in the detail view. Let's start with the master view. To add navigation to the master view, perform the following steps: Open MasterView.cs and add an event handler named NewParkClicked. In the event handler, invoke the NewParkClicked command on MasterViewModel: protected void NewParkClicked(object sender,       EventArgs e){   ((MasterViewModel)ViewModel).           NewParkClicked.Execute ();} In ViewDidLoad(), add UIBarButtonItem to NavigationItem for MasterView and assign the NewParkClicked event handler to it: NavigationItem.SetRightBarButtonItem(   new UIBarButtonItem(UIBarButtonSystemItem.Add,       NewParkClicked), true); To add navigation to the details view, perform the following steps: Open DetailView.cs and add an event handler named EditParkClicked. In the event handler, invoke the EditParkClicked command on DetailViewModel: protected void EditParkClicked (object sender,   EventArgs e){   ((DetailViewModel)ViewModel).EditPark.Execute ();} In ViewDidLoad(), add UIBarButtonItem to NavigationItem for MasterView, and assign the EditParkClicked event handler to it: NavigationItem.SetRightBarButtonItem(   new UIBarButtonItem(UIBarButtonSystemItem.Edit,       EditParkClicked), true); Refreshing the master view list One last detail that needs to be taken care of is to refresh the UITableView control on MasterView when items have been changed on EditView. To refresh the master view list, perform the following steps: Open MasterView.cs and call ReloadData() on parksTableView within the ViewDidAppear() method of MasterView: public override void ViewDidAppear (bool animated){   base.ViewDidAppear (animated);   parksTableView.ReloadData();} Compile and run NationalParks.iOS. You should now have a fully functional app that has the ability to create new parks and edit existing parks. Changes made to EditView should automatically be reflected in MasterView and DetailVIew. Considering the pros and cons After completing our work, we now have the basis to make some fundamental observations. Let's start with the pros: MvvmCross definitely increases the amount of code that can be reused across platforms. The ViewModels house the data required by the View, the logic required to obtain and transform the data in preparation for viewing, and the logic triggered by user interactions in the form of commands. In our sample app, the ViewModels were somewhat simple; however, the more complex the app, the more reuse will likely be gained. As MvvmCross relies on the use of each platform's native UI frameworks, each app has a native look and feel and we have a natural layer that implements platform-specific logic when required. The data binding capabilities of MvvmCross also eliminate a great deal of tedious code that would otherwise have to be written. All of these positives are not necessarily free; let's look at some cons: The first con is complexity; you have to learn another framework on top of Xamarin, Android, and iOS. In some ways, MvvmCross forces you to align the way your apps work across platforms to achieve the most reuse. As the presentation logic is contained in the ViewModels, the views are coerced into aligning with them. The more your UI deviates across platforms; the less likely it will be that you can actually reuse ViewModels. With these things in mind, I would definitely consider using MvvmCross for a cross-platform mobile project. Yes, you need to learn an addition framework and yes, you will likely have to align the way some of the apps are laid out, but I think MvvmCross provides enough value and flexibility to make these issues workable. I'm a big fan of reuse and MvvmCross definitely pushes reuse to the next level. Summary In this article, we reviewed the high-level concepts of MvvmCross and worked through a practical exercise in order to convert the national parks apps to use the MvvmCross framework and the increase code reuse. In the next article, we will follow a similar approach to exploring the Xamarin.Forms framework in order to evaluate how its use can affect code reuse. Resources for Article: Further resources on this subject: XamChat – a Cross-platform App [Article] Configuring Your Operating System [Article] Updating data in the background [Article]
Read more
  • 0
  • 0
  • 6466

article-image-deep-customization-bootstrap
Packt
19 Dec 2014
8 min read
Save for later

Deep Customization of Bootstrap

Packt
19 Dec 2014
8 min read
This article is written by Aravind Shenoy and Ulrich Sossou, the authors of the book, Learning Bootstrap. It will introduce you to the concept of deep customization of Bootstrap. (For more resources related to this topic, see here.) Adding your own style sheet works when you are trying to do something quick or when the modifications are minimal. Customizing Bootstrap beyond small changes involves using the uncompiled Bootstrap source code. The Bootstrap CSS source code is written in LESS with variables and mixins to allow easy customization. LESS is an open source CSS preprocessor with cool features used to speed up your development time. LESS allows you to engage an efficient and modular style of working making it easier to maintain your CSS styling in your projects. The advantages of using variables in LESS are profound. You can reuse the same code many times thereby following the write once, use anywhere paradigm. Variables can be globally declared, which allows you to specify certain values in a single place. This needs to be updated only once if changes are required. LESS variables allow you to specify widely-used values such as colors, font family, and sizes in a single file. By modifying a single variable, the changes will be reflected in all the Bootstrap components that use it; for example, to change the background color of the body element to green (#00FF00 is the hexadecimal code for green), all you need to do is change the value of the variable called @body-bg in Bootstrap as shown in the following code: @body-bg: #00FF00; Mixins are similar to variables but for whole classes. Mixins enable you to embed the properties of a class into another. It allows you to group multiple code lines together so that it can be used numerous times across the style sheet. Mixins can also be used alongside variables and functions resulting in multiple inheritances; for example, to add clearfix to an article, you can use the .clearfix mixin as shown in the left column of the following table. It will result in all clearfix declarations included in the compiled CSS code shown in the right column: Mixin CSS code article { .clearfix; }   { article:before, article:after { content: " "; // 1 display: table; // 2 } article:after { clear: both; } }   A clearfix mixin is a way for an element to automatically clear after itself, so that you don't need to add additional markup. It's generally used in float layouts, where elements are floated to be stacked horizontally. Let's look at a pragmatic example to understand how this kind of customization is used in a real-time scenario: Download and unzip the Bootstrap files into a folder. Create an HTML file called bootstrap_example and save it in the same folder where you saved the Bootstrap files. Add the following code to it: <!DOCTYPE html> <html> <head> <title>BootStrap with Packt</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial- scale=1.0"> <!-- Downloaded Bootstrap CSS --> <link href="css/bootstrap.css" rel="stylesheet"> <!-- JavaScript plugins (requires jQuery) --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/ jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script> </head> <body> <h1>Welcome to Packt</h1> <button type="button" class="btn btn-default btn-lg" id="packt">PACKT LESSONS</button> </body> </html> The output of this code upon execution will be as follows: The Bootstrap folder includes the following folders and file:     css     fonts     js     bootstrap_example.html This Bootstrap folder is shown in the following screenshot: Since we are going to use the Bootstrap source code now, let's download the ZIP file and keep it at any location. Unzip it, and we can see the contents of the folder as shown in the following screenshot: Let's now create a new folder called bootstrap in the css folder. The contents of our css folder will appear as displayed in the following screenshot: Copy the contents of the less folder from the source code and paste it into the newly created bootstrap folder inside the css folder. Thus, the contents of the same bootstrap folder within the css folder will appear as displayed in the following screenshot: In the bootstrap folder, look for the variable.less file and open it using Notepad or Notepad++. In this example, we are using a simple Notepad, and on opening the variable.less file with Notepad, we can see the contents of the file as shown in the following screenshot: Currently, we can see @body-bg is assigned the default value #fff as the color code. Change the background color of the body element to green by assigning the value #00ff00 to it. Save the file and later on, look for the bootstrap.less file in the bootstrap folder. In the next step, we are going to use WinLess. Open WinLess and add the contents of the bootstrap folder to it. In the folder pane, you will see all the less files loaded as shown in the following screenshot:   Now, we need to uncheck all the files and only select the bootstrap.less file as shown in following screenshot:  Click on Compile. This will compile your bootstrap.less file to bootstrap.css. Copy the newly compiled bootstrap.css file from the bootstrap folder and paste it into the css folder thereby replacing the original bootstrap.css file. Now that we have the updated bootstrap.css file, go back to bootstrap_example.html and execute it. Upon execution, the output of the code would be as follows:  Thus, we can see that the background color of the <body> element turns to green as we have altered it globally in the variables.less file that was linked to the bootstrap.less file, which was later compiled to bootstrap.css by WinLess. We can also use LESS variables and mixins to customize Bootstrap. We can import the Bootstrap files and add our customizations. Let's now create our own less file called styles.less in the css folder. We will now include the Bootstrap files by adding the following line of code in the styles.less file: @import "./bootstrap/bootstrap.less"; We have given the path,./bootstrap/bootstrap.less as per the location of the bootstrap.less file. Remember to give the appropriate path if you have placed it at any other location. Now, let's try a few customizations and add the following code to styles.less: @body-bg: #FFA500; @padding-large-horizontal: 40px; @font-size-base: 7px; @line-height-base: 9px; @border-radius-large: 75px; The next step is to compile the styles.less file to styles.css. We will again use WinLess for this purpose. You have to uncheck all the options and select only styles.less to be compiled:  On compilation, the styles.css file will contain all the CSS declarations from Bootstrap. The next step would be to add the styles.css stylesheet to the bootstrap_example.html file.So your HTML code will look like this: <!DOCTYPE html> <html> <head> <title>BootStrap with Packt</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial- scale=1.0"> <!-- Downloaded Bootstrap CSS --> <link href="css/bootstrap.css" rel="stylesheet"> <!-- JavaScript plugins (requires jQuery) --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/ jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script> <link href="css/styles.css" rel="stylesheet"> </head> <body> <h1>Welcome to Packt</h1> <button type="button" class="btn btn-default btn-lg" id="packt">PACKT LESSONS</button> </body> </html> The output of the code is as follows: Since we changed the background color to orange (#ffa500), created a border radius, and defined the font-size-base and line-height-base, the output on execution was as displayed in the preceding screenshot. The LESS variables should be added to the styles.less file after the Bootstrap import so that they override the variables defined in the Bootstrap files. In short, all the custom code you write should be added after the Bootstrap import. Summary Therefore, we had a look at the procedure to implement Deep Customization in Bootstrap. However, we are still at the start of the journey. The learning curve is always steep as there is so much more to learn. Learning is always an ongoing process and it would never cease to exist. Thus, there is still a long way to go and in a pragmatic sense, the journey is the destination. Resources for Article: Further resources on this subject: Creating attention-grabbing pricing tables [article] Getting Started with Bootstrap [article] Bootstrap 3.0 is Mobile First [article]
Read more
  • 0
  • 0
  • 2133
article-image-building-robots-can-walk
Packt
19 Dec 2014
15 min read
Save for later

Building robots that can walk

Packt
19 Dec 2014
15 min read
In this article, by Richard Grimmett, author of Mastering BeagleBone Robotics, you'll build a quadruped, that is, a robot with four legs. You'll be using 12 servos so that each leg has three points that can move, or three Degrees of Freedom (DOF). In this project, you'll control 12 servos at the same time, so it will make more sense to use an external servo controller that can supply the control signals and supply voltages for all the 12 servos. (For more resources related to this topic, see here.) Since servos are the main component of this project, it is perhaps useful to go through a tutorial on servos and how to control them. Working of servomotors Servomotors are somewhat similar to DC motors; however, there is an important difference. While DC motors are generally designed to move in a continuous way—rotating 360 degrees at a given speed—servos are generally designed to move within a limited set of angles. In other words, in the case of a DC motor, you would generally want your motors to spin with continuous rotation speed that you control. But in the case of a servomotor, you would want your motor to move to a specific position that you control. This is done by sending a Pulse-Width-Modulated (PWM) signal to the control connector of the servo. PWM simply means that you are going to change the length of each pulse of electrical energy in order to control something. In this case, the length of this pulse will control the angle of the servo, as shown in the following diagram: These pulses are sent out with a repetition rate of 60 Hz. You can position the servo to any angle by setting the correct control pulse. Building the quadruped platform You'll first need some parts so that you can build your quadruped robot. There are several kit possibilities out there, including the one available at www.trossenrobotics.com/p/PhantomX-AX-12-Quadruped.aspx. However, such kits can be expensive, so for this example, you'll create your own kit using a set of Lynxmotion parts. These are available from several online retailers such as robotshop.com. To build this quadruped, you'll need four legs, each leg requires two Lynxmotion parts. Here are the parts with their Robotshop part numbers: Quantity Description 1 Lynxmotion symmetric quadruped body kit: Mini QBK-02 2 Lynxmotion 3'' aluminum femur pair 2 Lynxmotion Robot Leg "A" pair (No servo) RL-01 4 Lynxmotion aluminum multi-purpose servo bracket Two Pack ASB-04 2 Ball bearing with flange: 3mm ID (pair) Product code: RB-Lyn-317 This last part a bearing you'll need to connect the leg to the body. You'll also need 12 servos of a standard size. There are several possible choices, but I personally like the Hitec servos. They are very inexpensive and you can get them from most hobby shops and online electronic retailers. Now, for a moment on the model of servo: Servos come in different model numbers, primarily based on the amount of torque they can generate. Torque is the force that the servo can exert to move the part connected to it. In this case, your servos will need to lift and move the weight associated with your quadruped, so you'll need a servo with enough torque to do this. I suggest that you use eight Hitec model HS-485HB servos. You'll use these for the servos attached to the end of the leg and for the body. Then you'll use four Hitec model HS-645MG servos for the middle of the leg; this is the servo that will require the highest amount of torque. You can use the 12 Hitec model HS-645MG servos instead, but they are more expensive than the HS-485 servos, so using two different servos will be less expensive. Here are the steps required to assemble the quadruped: Put the two parts of the lower right leg together and insert the servo with the servo mounting screws. It should look like this: Now connect this assembly to the interconnect part, like this: Complete the leg by connecting two of the servo brackets together at right angles, mounting the HS-645MG on one of the brackets, and then connecting this servo to the interconnect piece, like this: Put another right leg together. Now put two left legs together following the same preceding steps, but in left leg configuration. They look like this: The next step is to build the body kit. There are some instructions given at www.lynxmotion.com/images/html/sq3u-assembly.htm, but it should be like the following image: Then connect each leg to the body kit. First connect the empty servo bracket to the body using the bearing, as shown in the following image: Now connect the other servo to the empty servo bracket and the body, like this: After performing all the preceding steps, your quadruped should now look like this: Now that you have the basic hardware assembled, you can turn your attention to the electronics. Using a servo controller to control the servos To make your quadruped walk, you will first need to connect the servomotor controller to the servos. The servo controller you are going to use for this project is a simple servomotor controller utilizing USB from Pololu (Pololu item number 1354, available at pololu.com) that can control 18 servomotors. Here is an image of the unit: Make sure that you order the assembled version. This piece of hardware will turn USB commands from the BeagleBone Black into signals that control your servomotors. Pololu makes a number of different versions of this controller, each able to control a certain number of servos. In this case, you may want to choose the 18-servo version, so you can control all 12 servos with one controller and also add an additional servo to control the direction of a camera or sensor. You could also choose the 12-servo version. One advantage of the 18-servo controller is the ease of connecting power to the unit via screw-type connectors. There are two connections you'll need to make to the servo controller to get started: the first is to the servomotors and the second is to a battery. First, connect the servos to the controller. In order to be consistent, let's connect your 12 servos to the connections marked 0 through 11 on the controller using the configuration shown in the following table: Servo Connector Servo 0 Right front lower leg 1 Right front middle leg 2 Right front upper leg 3 Right rear lower leg 4 Right rear middle leg 5 Right rear upper leg 6 Left front lower leg 7 Left front middle leg 8 Left front upper leg 9 Left rear lower leg 10 Left rear middle leg 11 Left rear upper leg Here is an image of the back of the controller. This will tell us where to connect our servos: Now you need to connect the servomotor controller to your battery. For this project, you can use a 2S RC LiPo battery. The 2S means that the battery will have two cells, with an output voltage of 7.2 volts. It will supply the voltage and current needed by your servos, which can be of the order of 2 amperes. Here is an image of the battery: This battery will come with two connectors: one with large gauge wires for normal usage and a smaller connector used to connect to the battery recharger. You'll want to build connectors that can connect to the screw-type connectors of the servo controller. I purchased some XT60 connector pairs, soldered some wires to the mating connector of the battery, and screwed these into the servo controller. Your system is now functional. Now you'll connect the motor controller to your personal computer to check if you can communicate with it. To do this, connect a mini USB cable between the servo controller and your personal computer. Communicating with the servo controller via a PC Now that the hardware is connected, you can use some software provided by Pololu to control the servos. Let's do this using your personal computer. First download the Pololu software from www.pololu.com/docs/0J40/3.a and install it according to the instructions on the website. Once it is installed, run the software, and you should see something like the following screenshot: You will first need to change the configuration of the serial settings, so select the Serial Settings tab and you should see this: Make sure that USB Chained is selected; this will allow you to communicate with and control the motor controller over USB. Now go back to the main screen by selecting the Status tab, and now you can actually turn on the 12 servos. The screen should look like this screenshot: Now you can use the sliders to actually control the servos. Check that servo 0 moves the right front lower servo, servo 1 moves the right front middle servo, servo 2 moves the right front upper servo, and so on. You can also use this to center the servos. Set all the servos such that the slider is in the middle. Now unscrew the servo horn on each servo until the servos are centered at this location. At the zero degree location of all servos, your quadruped should look like this: Your quadruped is now ready to actually do something. Now you'll need to send the servos the electronic signals they need to move your quadruped. Connecting the servo controller to the BeagleBone Black You've checked the servomotor controller and the servos. You'll now connect the motor controller to the BeagleBone Black and make sure you can control the servos from it. Remove the USB cable from the PC and connect it to the BeagleBone Black. The entire system will look like this: Let's now talk to the motor controller by downloading the Linux code from Pololu at www.pololu.com/docs/0J40/3.b. Perhaps the best way is to log in to your BeagleBone Black using PuTTY, then type wget http://www.pololu.com/file/download/maestro-linux-100507.tar.gz?file_id=0J315. Then move the file by typing mv maestro-linux-100507.tar.gz?file_id=0J315 maestro-linux-100507.tar.gz. Unpack the file by typing tar –xzfv maestro_linux_011507.tar.gz. This will create a directory called maestro_linux. Go to that directory by typing cd maestro_linux and then type ls. You should see something like this: The README.txt document will give you explicit instructions on how to install the software. Unfortunately, you can't run MaestroControlCenter on your BeagleBone Black. Your version of Windows doesn't support the graphics, but you can control your servos using the UscCmd command-line application to ensure that they are connected and working correctly. First, type ./UscCmd --list and you should see something like the following screenshot: The unit sees our servo controller. By just typing ./UscCmd, you can see all the commands that you can send to your controller, as shown in the following screenshot: Note that although you can send a servo a specific target angle, the target is not in angle values, so it makes it a bit difficult to know where you are sending your servo. Try typing ./UscCmd --servo 0, 10. The servo will move to its maximum angle position. Type ./UscCmd – servo 0, 0 and it will prevent the servo from trying to move. In the next section, you'll write some Python code that will translate your angles to the commands that the servo controller will want to receive to move it to specific angle locations. If you didn't run the Windows version of Maestro Controller and set the Serial Settings to USB Chained, your motor controller might not respond. Rerun the Maestro Controller code and set the Serial Settings to USB Chained. Creating a program on Linux to control your quadruped You now know that you can talk to your servomotor controller, and move your servos. In this section, you'll create a Python program that will let you talk to your servos to move them to specific angles. Let's start with a simple program that will make your legged mobile robot's servos go to 90 degrees (the middle of the 0 to 180 degrees you can set). This particular controller uses bytes of information, so the code will translate the input of the channel and angle to numbers that the controller can understand. For more details, see http://www.pololu.com/docs/0J40. Here is the code to move all the connected servos to the 90 degree point: Here is an explanation of the code: #! /usr/bin/python: This first line allows you to make this Python file executable from the command line. import serial: This line imports the serial library. You need the serial library to talk to your unit via USB. def setAngle(ser, channel, angle): This function converts your desired setting of servo and angle into the serial command that the servomotor controller needs. ser = serial.Serial("/dev/ttyACM0", 9600): This opens the serial port connection to your servo controller. for i in range(0, 15): This is for all 16 servo possibilities. setAngle(ser, i, 90): This allows you to set each servo to the middle (home) position. The default would be to set each servo to 90 degrees. If the legs of your robot aren't in their middle position, you can adjust them by adjusting the position of the servo horns on each servo. To access the serial port, you'll need to make sure that you have the Python serial library. If you don't, then type sudo apt-get install python-serial. After you have installed the serial library, you can run your program by typing sudo python quad.py. Once you have the basic home position set, you can now ask your robot to do some things. Let's start by making your quadruped wave an arm. Here is the Python code that waves the arm: In this case, you are using the setAngle command to set your servos to manipulate your robot's front-right arm. The middle servo raises the arm, and the lower servo then goes back and forth between angle 100 and 130. One of the most basic actions you'll want your robot to do is to walk forward. Here is an example of how to manipulate the legs to make this happen: This program lifts and moves each leg forward one at a time, and then moves all the legs to home position, which moves the robot forward. Not the most elegant motion, but it does work. There are more sophisticated algorithms to make your quadruped walk as shown at http://letsmakerobots.com/node/35354 and https://www.youtube.com/watch?v=jWP3RnYa_tw. Once you have the program working, you'll want to package all of your hardware onto the mobile robot. You can make your robot do many amazing things: walk forward, walk backward, dance, turn around—any kinds of movements are possible. The best way to learn is to try new and different positions with the servos. Issuing voice commands to your quadruped You should now have a mobile platform that you can program to move in any number of ways. Unfortunately, you still have your LAN cable connected, so the platform isn't completely mobile. And once you have begun the program, you can't alter the behavior of your program. You'll need to modify your voice recognition program so that it can run your Python program when it gets a voice command. You have to make a simple modification to the continuous.c program in /home/ubuntu/pocketsphinx-0.8/src/programs. To do this, type cd /home/ubuntu/ pocketsphinx-0.8/src/programs, and then type emacs continuous.c. The changes will occur in the same section as your other voice commands and will look like this: The additions are pretty straightforward. Let's walk through them: else if (strcmp(word, "FORWARD") == 0): This checks the word as recognized by your voice command program. If it corresponds with the word FORWARD, it will execute everything inside the if statement. We use { } to tell the system which commands go with this else if clause. system("/home/ubuntu/maestro_linux/robot.py"): This is the program we will execute. In this case, our mobile platform will do whatever the robot.py program tells it to do. After doing this, you will need to recompile the program, so type make and the pocketSphinx_continuous executable will be created. Run the program by typing ./pocketSphinx_continuous. Don't forget the ./ at the start of this command or you'll run a different version of the program. When the program is running, you can disconnect the LAN cable, and the mobile platform will now take the forward voice command and execute your program. Summary You now have a robot than can walk! You can also add other sensors, like the ones you discovered for your tracked robot, sensors that can watch for barriers, or even a webcam. Resources for Article: Further resources on this subject: Protecting GPG Keys in BeagleBone [Article] Home Security by BeagleBone [Article] Introducing BeagleBoard [Article]
Read more
  • 0
  • 0
  • 9347

article-image-navigation-mesh-generation
Packt
19 Dec 2014
9 min read
Save for later

Navigation Mesh Generation

Packt
19 Dec 2014
9 min read
In this article by Curtis Bennett and Dan Violet Sagmiller, authors of the book Unity AI Programming Essentials, we will learn about navigation meshes in Unity. Navigation mesh generation controls how AI characters are able to travel around a game level and is one of the most important topics in game AI. In this article, we will provide an overview of navigation meshes and look at the algorithm for generating them. Then, we'll look at different options of customizing our navigation meshes better. To do this, we will be using RAIN 2.1.5, a popular AI plugin for Unity by Rival Theory, available for free at http://rivaltheory.com/rain/download/. In this article, you will learn about: How navigation mesh generation works and the algorithm behind it Advanced options for customizing navigation meshes Creating advanced navigation meshes with RAIN (For more resources related to this topic, see here.) An overview of a navigation mesh To use navigation meshes, also referred to as NavMeshes, effectively the first things we need to know are what exactly navigation meshes are and how they are created. A navigation mesh is a definition of the area an AI character could travel to in a level. It is a mesh, but it is not intended to be rendered or seen by the player, instead it is used by the AI system. A NavMesh usually does not cover all the area in a level (if it did we wouldn't need one) since it's just the area a character can walk. The mesh is also almost always a simplified version of the geometry. For instance, you could have a cave floor in a game with thousands of polygons along the bottom showing different details in the rock, but for the navigation mesh the areas would just be a handful of very large polys giving a simplified view of the level. The purpose of navigation mesh is to provide this simplified representation to the rest of the AI system a way to find a path between two points on a level for a character. This is its purpose; let's discuss how they are created. It used to be a common practice in the games industry to create navigation meshes manually. A designer or artist would take the completed level geometry and create one using standard polygon mesh modelling tools and save it out. As you might imagine, this allowed for nice, custom, efficient meshes, but was also a big time sink, since every time the level changed the navigation mesh would need to be manually edited and updated. In recent years, there has been more research in automatic navigation mesh generation. There are many approaches to automatic navigation mesh generation, but the most popular is Recast, originally developed and designed by Mikko Monomen. Recast takes in level geometry and a set of parameters defining the character, such as the size of the character and how big of steps it can take, and then does a multipass approach to filter and create the final NavMesh. The most important phase of this is voxelizing the level based on an inputted cell size. This means the level geometry is divided into voxels (cubes) creating a version of the level geometry where everything is partitioned into different boxes called cells. Then the geometry in each of these cells is analyzed and simplified based on its intersection with the sides of the boxes and is culled based on things such as the slope of the geometry or how big a step height is between geometry. This simplified geometry is then merged and triangulated to make a final navigation mesh that can be used by the AI system. The source code and more information on the original C++ implementation of Recast is available at https://github.com/memononen/recastnavigation. Advanced NavMesh parameters Now that we understand how navigation mesh generations works, let's look at the different parameters you can set to generate them in more detail. We'll look at how to do these with RAIN: Open Unity and create a new scene and a floor and some blocks for walls. Download RAIN from http://rivaltheory.com/rain/download/ and import it into your scene. Then go to RAIN | Create Navigation Mesh. Also right-click on the RAIN menu and choose Show Advanced Settings. The setup should look something like the following screenshot: Now let's look at some of the important parameters: Size: This is the overall size of the navigation mesh. You'll want the navigation mesh to cover your entire level and use this parameter instead of trying to scale up the navigation mesh through the Scale transform in the Inspector window. For our demo here, set the Size parameter to 20. Walkable Radius: This is an important parameter to define the character size of the mesh. Remember, each mesh will be matched to the size of a particular character, and this is the radius of the character. You can visualize the radius for a character by adding a Unity Sphere Collider script to your object (by going to Component | Physics | Sphere Collider) and adjusting the radius of the collider. Cell Size: This is also a very important parameter. During the voxel step of the Recast algorithm, this sets the size of the cubes to inspect the geometry. The smaller the size, the more detailed and finer mesh, but longer the processing time for Recast. A large cell size makes computation fast but loses detail. For example, here is a NavMesh from our demo with a cell size of 0.01: You can see the finer detail here. Here is the navigation mesh generated with a cell size of 0.1: Note the difference between the two screenshots. In the former, walking through the two walls lower down in our picture is possible, but in the latter with a larger cell size, there is no path even though the character radius is the same. Problems like this become greater with larger cell sizes. The following is a navigation mesh with a cell size of 1: As you can see, the detail becomes jumbled and the mesh itself becomes unusable. With such differing results, the big question is how large should a cell size be for a level? The answer is that it depends on the required result. However, one important consideration is that as the processing time to generate one is done during development and not at runtime even if it takes several minutes to generate a good mesh, it can be worth it to get a good result in the game. Setting a small cell size on a large level can cause mesh processing to take a significant amount of time and consume a lot of memory. It is a good practice to save the scene before attempting to generate a complex navigation mesh. The Size, Walkable Radius, and Cell Size parameters are the most important parameters when generating the navigation mesh, but there are more that are used to customize the mesh further: Max Slope: This is the largest slope that a character can walk on. This is how much a piece of geometry that is tilted can still be walked on. If you take the wall and rotate it, you can see it is walkable: The preceding is a screenshot of a walkable object with slope. Step Height: This is how high a character can step from one object to another. For example, if you have steps between two blocks, as shown in the following screenshot, this would define how far in height the blocks can be apart and whether the area is still considered walkable: This is a screenshot of the navigation mesh with step height set to connect adjacent blocks. Walkable Height: This is the vertical height that is needed for the character to walk. For example, in the previous illustration, the second block is not walkable underneath because of the walkable height. If you raise it to a least one unit off the ground and set the walkable height to 1, the area underneath would become walkable:   You can see a screenshot of the navigation mesh with walkable height set to allow going under the higher block. These are the most important parameters. There are some other parameters related to the visualization and to cull objects. We will look at culling more in the next section. Culling areas Being able to set up areas as walkable or not is an important part of creating a level. To demo this, let's divide the level into two parts and create a bridge between the two. Take our demo and duplicate the floor and pull it down. Then transform one of the walls to a bridge. Then, add two other pieces of geometry to mark areas that are dangerous to walk on, like lava. Here is an example setup: This is a basic scene with a bridge to cross. If you recreate the navigation mesh now, all of the geometry will be covered and the bridge won't be recognized. To fix this, you can create a new tag called Lava and tag the geometry under the bridge with it. Then, in the navigation meshes' RAIN component, add Lava to the unwalkable tags. If you then regenerate the mesh, only the bridge is walkable. This is a screenshot of a navigation mesh areas under bridge culled: Using layers and the walkable tag you can customize navigation meshes. Summary Navigation meshes are an important part of game AI. In this article, we looked at the different parameters to customize navigation meshes. We looked at things such as setting the character size and walkable slopes and discussed the importance of the cell size parameter. We then saw how to customize our mesh by tagging different areas as not walkable. This should be a good start for designing navigation meshes for your games. Resources for Article: Further resources on this subject: Components in Unity [article] Enemy and Friendly AIs [article] Introduction to AI [article]
Read more
  • 0
  • 0
  • 4378
Modal Close icon
Modal Close icon